From 1ebbb3384211726bfb1405c66d5a09b083c6d99d Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 2 Sep 2025 13:58:34 -0700 Subject: [PATCH] Rename "alias" filter to "command" and replace "@" character with "/" (#38) * Initial plan * Core Go code changes: rename alias to command, @ to /, update tests Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> * Complete alias to command rename: tests, documentation, schema updated Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> Co-authored-by: Peli de Halleux --- ....lock.yml => test-claude-command.lock.yml} | 36 ++-- ...claude-alias.md => test-claude-command.md} | 6 +- ...s.lock.yml => test-codex-command.lock.yml} | 36 ++-- ...t-codex-alias.md => test-codex-command.md} | 6 +- ...{alias-triggers.md => command-triggers.md} | 30 +-- docs/frontmatter.md | 4 +- docs/safe-outputs.md | 2 +- pkg/cli/templates/instructions.md | 16 +- pkg/parser/schema_test.go | 8 +- pkg/parser/schemas/main_workflow_schema.json | 14 +- pkg/workflow/alias.go | 92 --------- pkg/workflow/command.go | 92 +++++++++ .../{alias_test.go => command_test.go} | 48 ++--- pkg/workflow/compiler.go | 176 ++++++++--------- pkg/workflow/compiler_test.go | 181 +++++++++--------- pkg/workflow/concurrency.go | 18 +- pkg/workflow/concurrency_test.go | 6 +- pkg/workflow/team_member_test.go | 38 ++-- 18 files changed, 404 insertions(+), 405 deletions(-) rename .github/workflows/{test-claude-alias.lock.yml => test-claude-command.lock.yml} (98%) rename .github/workflows/{test-claude-alias.md => test-claude-command.md} (75%) rename .github/workflows/{test-codex-alias.lock.yml => test-codex-command.lock.yml} (98%) rename .github/workflows/{test-codex-alias.md => test-codex-command.md} (75%) rename docs/{alias-triggers.md => command-triggers.md} (71%) delete mode 100644 pkg/workflow/alias.go create mode 100644 pkg/workflow/command.go rename pkg/workflow/{alias_test.go => command_test.go} (69%) diff --git a/.github/workflows/test-claude-alias.lock.yml b/.github/workflows/test-claude-command.lock.yml similarity index 98% rename from .github/workflows/test-claude-alias.lock.yml rename to .github/workflows/test-claude-command.lock.yml index 81fc1e7d07..f431225b73 100644 --- a/.github/workflows/test-claude-alias.lock.yml +++ b/.github/workflows/test-claude-command.lock.yml @@ -2,7 +2,7 @@ # To update this file, edit the corresponding .md file and run: # gh aw compile -name: "Test Claude Alias" +name: "Test Claude Command" on: issues: types: [opened, edited, reopened] @@ -18,18 +18,18 @@ permissions: {} concurrency: group: "gh-aw-${{ github.workflow }}-${{ github.event.issue.number || github.event.pull_request.number }}" -run-name: "Test Claude Alias" +run-name: "Test Claude Command" jobs: task: - if: ((contains(github.event.issue.body, '@test-claude-alias')) || (contains(github.event.comment.body, '@test-claude-alias'))) || (contains(github.event.pull_request.body, '@test-claude-alias')) + if: ((contains(github.event.issue.body, '/test-claude-command')) || (contains(github.event.comment.body, '/test-claude-command'))) || (contains(github.event.pull_request.body, '/test-claude-command')) runs-on: ubuntu-latest outputs: text: ${{ steps.compute-text.outputs.text }} steps: - - name: Check team membership for alias workflow + - name: Check team membership for command workflow id: check-team-member - if: contains(github.event.issue.body, '@test-claude-alias') || contains(github.event.comment.body, '@test-claude-alias') || contains(github.event.pull_request.body, '@test-claude-alias') + if: contains(github.event.issue.body, '/test-claude-command') || contains(github.event.comment.body, '/test-claude-command') || contains(github.event.pull_request.body, '/test-claude-command') uses: actions/github-script@v7 with: script: | @@ -61,7 +61,7 @@ jobs: - name: Validate team membership if: steps.check-team-member.outputs.is_team_member == 'false' run: | - echo "❌ Access denied: Only team members can trigger alias workflows" + echo "❌ Access denied: Only team members can trigger command workflows" echo "User ${{ github.actor }} is not a team member" exit 1 - name: Compute current body text @@ -267,7 +267,7 @@ jobs: uses: actions/github-script@v7 env: GITHUB_AW_REACTION: eyes - GITHUB_AW_ALIAS: test-claude-alias + GITHUB_AW_COMMAND: test-claude-command with: script: | async function main() { @@ -424,7 +424,7 @@ jobs: } await main(); - test-claude-alias: + test-claude-command: needs: task runs-on: ubuntu-latest permissions: read-all @@ -487,7 +487,7 @@ jobs: run: | mkdir -p /tmp/aw-prompts cat > /tmp/aw-prompts/prompt.txt << 'EOF' - Add a reply comment to issue #${{ github.event.issue.number }} answering the question "${{ needs.task.outputs.text }}" given the context of the repo, starting with saying you're Claude. If there is no alias write out a haiku about the repo. + Add a reply comment to issue #${{ github.event.issue.number }} answering the question "${{ needs.task.outputs.text }}" given the context of the repo, starting with saying you're Claude. If there is no command write out a haiku about the repo. --- @@ -535,7 +535,7 @@ jobs: engine_name: "Claude Code", model: "", version: "", - workflow_name: "Test Claude Alias", + workflow_name: "Test Claude Command", experimental: false, supports_tools_whitelist: true, supports_http_transport: true, @@ -635,13 +635,13 @@ jobs: run: | # Copy the detailed execution file from Agentic Action if available if [ -n "${{ steps.agentic_execution.outputs.execution_file }}" ] && [ -f "${{ steps.agentic_execution.outputs.execution_file }}" ]; then - cp ${{ steps.agentic_execution.outputs.execution_file }} /tmp/test-claude-alias.log + cp ${{ steps.agentic_execution.outputs.execution_file }} /tmp/test-claude-command.log else - echo "No execution file output found from Agentic Action" >> /tmp/test-claude-alias.log + echo "No execution file output found from Agentic Action" >> /tmp/test-claude-command.log fi # Ensure log file exists - touch /tmp/test-claude-alias.log + touch /tmp/test-claude-command.log - name: Check if workflow-complete.txt exists, if so upload it id: check_file run: | @@ -972,7 +972,7 @@ jobs: if: always() uses: actions/github-script@v7 env: - AGENT_LOG_FILE: /tmp/test-claude-alias.log + AGENT_LOG_FILE: /tmp/test-claude-command.log with: script: | function main() { @@ -1247,8 +1247,8 @@ jobs: if: always() uses: actions/upload-artifact@v4 with: - name: test-claude-alias.log - path: /tmp/test-claude-alias.log + name: test-claude-command.log + path: /tmp/test-claude-command.log if-no-files-found: warn - name: Generate git patch if: always() @@ -1314,7 +1314,7 @@ jobs: if-no-files-found: ignore create_issue_comment: - needs: test-claude-alias + needs: test-claude-command if: github.event.issue.number || github.event.pull_request.number runs-on: ubuntu-latest permissions: @@ -1330,7 +1330,7 @@ jobs: id: create_comment uses: actions/github-script@v7 env: - GITHUB_AW_AGENT_OUTPUT: ${{ needs.test-claude-alias.outputs.output }} + GITHUB_AW_AGENT_OUTPUT: ${{ needs.test-claude-command.outputs.output }} with: script: | async function main() { diff --git a/.github/workflows/test-claude-alias.md b/.github/workflows/test-claude-command.md similarity index 75% rename from .github/workflows/test-claude-alias.md rename to .github/workflows/test-claude-command.md index e73c9b7644..8733b0dbf5 100644 --- a/.github/workflows/test-claude-alias.md +++ b/.github/workflows/test-claude-command.md @@ -1,7 +1,7 @@ --- on: - alias: - name: test-claude-alias + command: + name: test-claude-command reaction: eyes engine: @@ -11,5 +11,5 @@ safe-outputs: add-issue-comment: --- -Add a reply comment to issue #${{ github.event.issue.number }} answering the question "${{ needs.task.outputs.text }}" given the context of the repo, starting with saying you're Claude. If there is no alias write out a haiku about the repo. +Add a reply comment to issue #${{ github.event.issue.number }} answering the question "${{ needs.task.outputs.text }}" given the context of the repo, starting with saying you're Claude. If there is no command write out a haiku about the repo. diff --git a/.github/workflows/test-codex-alias.lock.yml b/.github/workflows/test-codex-command.lock.yml similarity index 98% rename from .github/workflows/test-codex-alias.lock.yml rename to .github/workflows/test-codex-command.lock.yml index aaec95cc43..cc2be65202 100644 --- a/.github/workflows/test-codex-alias.lock.yml +++ b/.github/workflows/test-codex-command.lock.yml @@ -2,7 +2,7 @@ # To update this file, edit the corresponding .md file and run: # gh aw compile -name: "Test Codex Alias" +name: "Test Codex Command" on: issues: types: [opened, edited, reopened] @@ -18,18 +18,18 @@ permissions: {} concurrency: group: "gh-aw-${{ github.workflow }}-${{ github.event.issue.number || github.event.pull_request.number }}" -run-name: "Test Codex Alias" +run-name: "Test Codex Command" jobs: task: - if: ((contains(github.event.issue.body, '@test-codex-alias')) || (contains(github.event.comment.body, '@test-codex-alias'))) || (contains(github.event.pull_request.body, '@test-codex-alias')) + if: ((contains(github.event.issue.body, '/test-codex-command')) || (contains(github.event.comment.body, '/test-codex-command'))) || (contains(github.event.pull_request.body, '/test-codex-command')) runs-on: ubuntu-latest outputs: text: ${{ steps.compute-text.outputs.text }} steps: - - name: Check team membership for alias workflow + - name: Check team membership for command workflow id: check-team-member - if: contains(github.event.issue.body, '@test-codex-alias') || contains(github.event.comment.body, '@test-codex-alias') || contains(github.event.pull_request.body, '@test-codex-alias') + if: contains(github.event.issue.body, '/test-codex-command') || contains(github.event.comment.body, '/test-codex-command') || contains(github.event.pull_request.body, '/test-codex-command') uses: actions/github-script@v7 with: script: | @@ -61,7 +61,7 @@ jobs: - name: Validate team membership if: steps.check-team-member.outputs.is_team_member == 'false' run: | - echo "❌ Access denied: Only team members can trigger alias workflows" + echo "❌ Access denied: Only team members can trigger command workflows" echo "User ${{ github.actor }} is not a team member" exit 1 - name: Compute current body text @@ -267,7 +267,7 @@ jobs: uses: actions/github-script@v7 env: GITHUB_AW_REACTION: eyes - GITHUB_AW_ALIAS: test-codex-alias + GITHUB_AW_COMMAND: test-codex-command with: script: | async function main() { @@ -424,7 +424,7 @@ jobs: } await main(); - test-codex-alias: + test-codex-command: needs: task runs-on: ubuntu-latest permissions: read-all @@ -487,7 +487,7 @@ jobs: run: | mkdir -p /tmp/aw-prompts cat > /tmp/aw-prompts/prompt.txt << 'EOF' - Add a reply comment to issue #${{ github.event.issue.number }} answering the question "${{ needs.task.outputs.text }}" given the context of the repo, starting with saying you're Codex. If there is no alias write out a haiku about the repo. + Add a reply comment to issue #${{ github.event.issue.number }} answering the question "${{ needs.task.outputs.text }}" given the context of the repo, starting with saying you're Codex. If there is no command write out a haiku about the repo. --- @@ -535,7 +535,7 @@ jobs: engine_name: "Claude Code", model: "", version: "", - workflow_name: "Test Codex Alias", + workflow_name: "Test Codex Command", experimental: false, supports_tools_whitelist: true, supports_http_transport: true, @@ -635,13 +635,13 @@ jobs: run: | # Copy the detailed execution file from Agentic Action if available if [ -n "${{ steps.agentic_execution.outputs.execution_file }}" ] && [ -f "${{ steps.agentic_execution.outputs.execution_file }}" ]; then - cp ${{ steps.agentic_execution.outputs.execution_file }} /tmp/test-codex-alias.log + cp ${{ steps.agentic_execution.outputs.execution_file }} /tmp/test-codex-command.log else - echo "No execution file output found from Agentic Action" >> /tmp/test-codex-alias.log + echo "No execution file output found from Agentic Action" >> /tmp/test-codex-command.log fi # Ensure log file exists - touch /tmp/test-codex-alias.log + touch /tmp/test-codex-command.log - name: Check if workflow-complete.txt exists, if so upload it id: check_file run: | @@ -972,7 +972,7 @@ jobs: if: always() uses: actions/github-script@v7 env: - AGENT_LOG_FILE: /tmp/test-codex-alias.log + AGENT_LOG_FILE: /tmp/test-codex-command.log with: script: | function main() { @@ -1247,8 +1247,8 @@ jobs: if: always() uses: actions/upload-artifact@v4 with: - name: test-codex-alias.log - path: /tmp/test-codex-alias.log + name: test-codex-command.log + path: /tmp/test-codex-command.log if-no-files-found: warn - name: Generate git patch if: always() @@ -1314,7 +1314,7 @@ jobs: if-no-files-found: ignore create_issue_comment: - needs: test-codex-alias + needs: test-codex-command if: github.event.issue.number || github.event.pull_request.number runs-on: ubuntu-latest permissions: @@ -1330,7 +1330,7 @@ jobs: id: create_comment uses: actions/github-script@v7 env: - GITHUB_AW_AGENT_OUTPUT: ${{ needs.test-codex-alias.outputs.output }} + GITHUB_AW_AGENT_OUTPUT: ${{ needs.test-codex-command.outputs.output }} with: script: | async function main() { diff --git a/.github/workflows/test-codex-alias.md b/.github/workflows/test-codex-command.md similarity index 75% rename from .github/workflows/test-codex-alias.md rename to .github/workflows/test-codex-command.md index d7431f569f..3c22490ec4 100644 --- a/.github/workflows/test-codex-alias.md +++ b/.github/workflows/test-codex-command.md @@ -1,7 +1,7 @@ --- on: - alias: - name: test-codex-alias + command: + name: test-codex-command reaction: eyes engine: @@ -11,5 +11,5 @@ safe-outputs: add-issue-comment: --- -Add a reply comment to issue #${{ github.event.issue.number }} answering the question "${{ needs.task.outputs.text }}" given the context of the repo, starting with saying you're Codex. If there is no alias write out a haiku about the repo. +Add a reply comment to issue #${{ github.event.issue.number }} answering the question "${{ needs.task.outputs.text }}" given the context of the repo, starting with saying you're Codex. If there is no command write out a haiku about the repo. diff --git a/docs/alias-triggers.md b/docs/command-triggers.md similarity index 71% rename from docs/alias-triggers.md rename to docs/command-triggers.md index b02757f060..cd082e216d 100644 --- a/docs/alias-triggers.md +++ b/docs/command-triggers.md @@ -1,43 +1,43 @@ -# 🏷️ Alias Triggers +# 🏷️ Command Triggers -This guide covers alias triggers and context text functionality for agentic workflows. +This guide covers command triggers and context text functionality for agentic workflows. -## Special `alias:` Trigger +## Special `command:` Trigger -GitHub Agentic Workflows add the convenience `alias:` trigger to create workflows that respond to `@mentions` in issues and comments. +GitHub Agentic Workflows add the convenience `command:` trigger to create workflows that respond to `/mentions` in issues and comments. ```yaml on: - alias: + command: name: my-bot # Optional: defaults to filename without .md extension ``` This automatically creates: - Issue and PR triggers (`opened`, `edited`, `reopened`) - Comment triggers (`created`, `edited`) -- Conditional execution matching `@alias-name` mentions +- Conditional execution matching `/command-name` mentions -You can combine `alias:` with other events like `workflow_dispatch` or `schedule`: +You can combine `command:` with other events like `workflow_dispatch` or `schedule`: ```yaml on: - alias: + command: name: my-bot workflow_dispatch: schedule: - cron: "0 9 * * 1" ``` -**Note**: You cannot combine `alias` with `issues`, `issue_comment`, or `pull_request` as they would conflict. +**Note**: You cannot combine `command` with `issues`, `issue_comment`, or `pull_request` as they would conflict. **Note**: Using this feature results in the addition of `.github/actions/check-team-member/action.yml` file to the repository when the workflow is compiled. This file is used to check if the user triggering the workflow has appropriate permissions to operate in the repository. -### Example alias workflow +### Example command workflow ```markdown --- on: - alias: + command: name: summarize-issue permissions: issues: write @@ -48,7 +48,7 @@ tools: # Issue Summarizer -When someone mentions @summarize-issue in an issue or comment, +When someone mentions /summarize-issue in an issue or comment, analyze and provide a helpful summary. The current context text is: "${{ needs.task.outputs.text }}" @@ -74,16 +74,16 @@ All workflows have access to a special computed `needs.task.outputs.text` value ## Visual Feedback with Reactions -Alias workflows can provide immediate visual feedback by adding reactions to triggering comments and automatically editing them with workflow run links: +Command workflows can provide immediate visual feedback by adding reactions to triggering comments and automatically editing them with workflow run links: ```yaml on: - alias: + command: name: my-bot reaction: "eyes" ``` -When someone mentions `@my-bot` in a comment, the workflow will: +When someone mentions `/my-bot` in a comment, the workflow will: 1. Add the specified emoji reaction (👀) to the comment 2. Automatically edit the comment to include a link to the workflow run diff --git a/docs/frontmatter.md b/docs/frontmatter.md index 6ba0718e0f..c8601a1fff 100644 --- a/docs/frontmatter.md +++ b/docs/frontmatter.md @@ -88,7 +88,7 @@ on: - `rocket` (🚀) - `eyes` (👀) -**Enhanced functionality**: When using the `reaction:` feature with alias workflows, the system will also automatically edit the triggering comment to include a link to the workflow run. This provides users with immediate feedback and easy access to view the workflow execution. For non-alias workflows, only the reaction is added without comment editing. +**Enhanced functionality**: When using the `reaction:` feature with command workflows, the system will also automatically edit the triggering comment to include a link to the workflow run. This provides users with immediate feedback and easy access to view the workflow execution. For non-command workflows, only the reaction is added without comment editing. **Note**: This feature uses inline JavaScript code with `actions/github-script@v7` to add reactions and edit comments, so no additional action files are created in the repository. @@ -110,7 +110,7 @@ on: workflow_dispatch: ``` -An additional kind of trigger called `alias:` is supported, see [Alias Triggers](alias-triggers.md) for special `@mention` triggers and context text functionality. +An additional kind of trigger called `command:` is supported, see [Command Triggers](command-triggers.md) for special `/mention` triggers and context text functionality. ## Permissions (`permissions:`) diff --git a/docs/safe-outputs.md b/docs/safe-outputs.md index daae47b5b1..2ce52d88e5 100644 --- a/docs/safe-outputs.md +++ b/docs/safe-outputs.md @@ -206,5 +206,5 @@ safe-outputs: - [Frontmatter Options](frontmatter.md) - All configuration options for workflows - [Workflow Structure](workflow-structure.md) - Directory layout and organization -- [Alias Triggers](alias-triggers.md) - Special @mention triggers and context text +- [Command Triggers](command-triggers.md) - Special /mention triggers and context text - [Commands](commands.md) - CLI commands for workflow management diff --git a/pkg/cli/templates/instructions.md b/pkg/cli/templates/instructions.md index 00312c6cf0..15f99c07b6 100644 --- a/pkg/cli/templates/instructions.md +++ b/pkg/cli/templates/instructions.md @@ -41,7 +41,7 @@ The YAML frontmatter supports these fields: - **`on:`** - Workflow triggers (required) - String: `"push"`, `"issues"`, etc. - Object: Complex trigger configuration - - Special: `alias:` for @mention triggers + - Special: `command:` for /mention triggers - **`stop-after:`** - Can be included in the `on:` object to set a deadline for workflow execution. Supports absolute timestamps ("YYYY-MM-DD HH:MM:SS") or relative time deltas (+25h, +3d, +1d12h30m). Uses precise date calculations that account for varying month lengths. - **`permissions:`** - GitHub token permissions @@ -195,14 +195,14 @@ on: workflow_dispatch: # Manual trigger ``` -### Alias Triggers (@mentions) +### Command Triggers (/mentions) ```yaml on: - alias: - name: my-bot # Responds to @my-bot in issues/comments + command: + name: my-bot # Responds to /my-bot in issues/comments ``` -This automatically creates conditions to match `@my-bot` mentions in issue bodies and comments. +This automatically creates conditions to match `/my-bot` mentions in issue bodies and comments. ### Semi-Active Agent Pattern ```yaml @@ -573,11 +573,11 @@ Research latest developments in ${{ github.repository }}: - Create summary issue ``` -### @mention Response Bot +### /mention Response Bot ```markdown --- on: - alias: + command: name: helper-bot permissions: issues: write @@ -588,7 +588,7 @@ tools: # Helper Bot -Respond to @helper-bot mentions with helpful information. +Respond to /helper-bot mentions with helpful information. ``` ## Workflow Monitoring and Analysis diff --git a/pkg/parser/schema_test.go b/pkg/parser/schema_test.go index d3775e82e1..5290502106 100644 --- a/pkg/parser/schema_test.go +++ b/pkg/parser/schema_test.go @@ -32,7 +32,7 @@ func TestValidateMainWorkflowFrontmatterWithSchema(t *testing.T) { "steps": []string{"step1"}, "engine": "claude", "tools": map[string]any{"github": "test"}, - "alias": "test-workflow", + "command": "test-workflow", }, wantErr: false, }, @@ -194,11 +194,11 @@ func TestValidateMainWorkflowFrontmatterWithSchema(t *testing.T) { wantErr: false, }, { - name: "valid frontmatter with alias trigger", + name: "valid frontmatter with command trigger", frontmatter: map[string]any{ "on": map[string]any{ - "alias": map[string]any{ - "name": "test-alias", + "command": map[string]any{ + "name": "test-command", }, }, "permissions": map[string]any{ diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index 3b97968d08..e12aab49c1 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -17,20 +17,20 @@ "type": "object", "description": "Complex trigger configuration", "properties": { - "alias": { - "description": "Alias trigger configuration for @mentions", + "command": { + "description": "Command trigger configuration for /mentions", "oneOf": [ { "type": "null", - "description": "Null alias (defaults to filename)" + "description": "Null command (defaults to filename)" }, { "type": "object", - "description": "Alias configuration object", + "description": "Command configuration object", "properties": { "name": { "type": "string", - "description": "Alias name (defaults to filename without .md extension)" + "description": "Command name (defaults to filename without .md extension)" } }, "additionalProperties": false @@ -852,9 +852,9 @@ ] } }, - "alias": { + "command": { "type": "string", - "description": "Alias name for the workflow" + "description": "Command name for the workflow" }, "cache": { "description": "Cache configuration for workflow (uses actions/cache syntax)", diff --git a/pkg/workflow/alias.go b/pkg/workflow/alias.go deleted file mode 100644 index cf9af5b10e..0000000000 --- a/pkg/workflow/alias.go +++ /dev/null @@ -1,92 +0,0 @@ -package workflow - -import "fmt" - -// buildAliasOnlyCondition creates a condition that only applies to alias mentions in comment-related events -// Unlike buildEventAwareAliasCondition, this does NOT allow non-comment events to pass through -func buildAliasOnlyCondition(aliasName string) ConditionNode { - // Define the alias condition using proper expression nodes - aliasText := fmt.Sprintf("@%s", aliasName) - - // Build alias checks for different content sources using expression nodes - issueBodyCheck := BuildContains( - BuildPropertyAccess("github.event.issue.body"), - BuildStringLiteral(aliasText), - ) - commentBodyCheck := BuildContains( - BuildPropertyAccess("github.event.comment.body"), - BuildStringLiteral(aliasText), - ) - prBodyCheck := BuildContains( - BuildPropertyAccess("github.event.pull_request.body"), - BuildStringLiteral(aliasText), - ) - - // Combine all alias checks with OR - only true when alias is mentioned - return &DisjunctionNode{ - Terms: []ConditionNode{ - issueBodyCheck, - commentBodyCheck, - prBodyCheck, - }, - } -} - -// buildEventAwareAliasCondition creates a condition that only applies alias checks to comment-related events -func buildEventAwareAliasCondition(aliasName string, hasOtherEvents bool) ConditionNode { - // Define the alias condition using proper expression nodes - aliasText := fmt.Sprintf("@%s", aliasName) - - // Build alias checks for different content sources using expression nodes - issueBodyCheck := BuildContains( - BuildPropertyAccess("github.event.issue.body"), - BuildStringLiteral(aliasText), - ) - commentBodyCheck := BuildContains( - BuildPropertyAccess("github.event.comment.body"), - BuildStringLiteral(aliasText), - ) - prBodyCheck := BuildContains( - BuildPropertyAccess("github.event.pull_request.body"), - BuildStringLiteral(aliasText), - ) - - // Combine all alias checks with OR - aliasCondition := &OrNode{ - Left: &OrNode{ - Left: issueBodyCheck, - Right: commentBodyCheck, - }, - Right: prBodyCheck, - } - - if !hasOtherEvents { - // If there are no other events, just use the simple alias condition - return aliasCondition - } - - // Define which events should be checked for alias using expression nodes - commentEventChecks := &DisjunctionNode{ - Terms: []ConditionNode{ - BuildEventTypeEquals("issues"), - BuildEventTypeEquals("issue_comment"), - BuildEventTypeEquals("pull_request"), - BuildEventTypeEquals("pull_request_review_comment"), - }, - } - - // For comment events: check alias; for other events: allow unconditionally - commentEventCheck := &AndNode{ - Left: commentEventChecks, - Right: aliasCondition, - } - - // Allow all non-comment events to run - nonCommentEvents := &NotNode{Child: commentEventChecks} - - // Combine: (comment events && alias check) || (non-comment events) - return &OrNode{ - Left: commentEventCheck, - Right: nonCommentEvents, - } -} diff --git a/pkg/workflow/command.go b/pkg/workflow/command.go new file mode 100644 index 0000000000..ef884b824e --- /dev/null +++ b/pkg/workflow/command.go @@ -0,0 +1,92 @@ +package workflow + +import "fmt" + +// buildCommandOnlyCondition creates a condition that only applies to command mentions in comment-related events +// Unlike buildEventAwareCommandCondition, this does NOT allow non-comment events to pass through +func buildCommandOnlyCondition(commandName string) ConditionNode { + // Define the command condition using proper expression nodes + commandText := fmt.Sprintf("/%s", commandName) + + // Build command checks for different content sources using expression nodes + issueBodyCheck := BuildContains( + BuildPropertyAccess("github.event.issue.body"), + BuildStringLiteral(commandText), + ) + commentBodyCheck := BuildContains( + BuildPropertyAccess("github.event.comment.body"), + BuildStringLiteral(commandText), + ) + prBodyCheck := BuildContains( + BuildPropertyAccess("github.event.pull_request.body"), + BuildStringLiteral(commandText), + ) + + // Combine all command checks with OR - only true when command is mentioned + return &DisjunctionNode{ + Terms: []ConditionNode{ + issueBodyCheck, + commentBodyCheck, + prBodyCheck, + }, + } +} + +// buildEventAwareCommandCondition creates a condition that only applies command checks to comment-related events +func buildEventAwareCommandCondition(commandName string, hasOtherEvents bool) ConditionNode { + // Define the command condition using proper expression nodes + commandText := fmt.Sprintf("/%s", commandName) + + // Build command checks for different content sources using expression nodes + issueBodyCheck := BuildContains( + BuildPropertyAccess("github.event.issue.body"), + BuildStringLiteral(commandText), + ) + commentBodyCheck := BuildContains( + BuildPropertyAccess("github.event.comment.body"), + BuildStringLiteral(commandText), + ) + prBodyCheck := BuildContains( + BuildPropertyAccess("github.event.pull_request.body"), + BuildStringLiteral(commandText), + ) + + // Combine all command checks with OR + commandCondition := &OrNode{ + Left: &OrNode{ + Left: issueBodyCheck, + Right: commentBodyCheck, + }, + Right: prBodyCheck, + } + + if !hasOtherEvents { + // If there are no other events, just use the simple command condition + return commandCondition + } + + // Define which events should be checked for command using expression nodes + commentEventChecks := &DisjunctionNode{ + Terms: []ConditionNode{ + BuildEventTypeEquals("issues"), + BuildEventTypeEquals("issue_comment"), + BuildEventTypeEquals("pull_request"), + BuildEventTypeEquals("pull_request_review_comment"), + }, + } + + // For comment events: check command; for other events: allow unconditionally + commentEventCheck := &AndNode{ + Left: commentEventChecks, + Right: commandCondition, + } + + // Allow all non-comment events to run + nonCommentEvents := &NotNode{Child: commentEventChecks} + + // Combine: (comment events && command check) || (non-comment events) + return &OrNode{ + Left: commentEventCheck, + Right: nonCommentEvents, + } +} diff --git a/pkg/workflow/alias_test.go b/pkg/workflow/command_test.go similarity index 69% rename from pkg/workflow/alias_test.go rename to pkg/workflow/command_test.go index 18f8abfa45..4f551bcc02 100644 --- a/pkg/workflow/alias_test.go +++ b/pkg/workflow/command_test.go @@ -7,10 +7,10 @@ import ( "testing" ) -// TestEventAwareAliasConditions tests that alias conditions are properly applied only to comment-related events -func TestEventAwareAliasConditions(t *testing.T) { +// TestEventAwareCommandConditions tests that command conditions are properly applied only to comment-related events +func TestEventAwareCommandConditions(t *testing.T) { // Create temporary directory for test files - tmpDir, err := os.MkdirTemp("", "workflow-event-aware-alias-test") + tmpDir, err := os.MkdirTemp("", "workflow-event-aware-command-test") if err != nil { t.Fatal(err) } @@ -22,28 +22,28 @@ func TestEventAwareAliasConditions(t *testing.T) { name string frontmatter string filename string - expectedSimpleCondition bool // true if should use simple condition (alias only) - expectedEventAware bool // true if should use event-aware condition (alias + other events) + expectedSimpleCondition bool // true if should use simple condition (command only) + expectedEventAware bool // true if should use event-aware condition (command + other events) }{ { - name: "alias only should use simple condition", + name: "command only should use simple condition", frontmatter: `--- on: - alias: + command: name: simple-bot tools: github: allowed: [list_issues] ---`, - filename: "simple-alias.md", + filename: "simple-command.md", expectedSimpleCondition: true, expectedEventAware: false, }, { - name: "alias with push should use event-aware condition", + name: "command with push should use event-aware condition", frontmatter: `--- on: - alias: + command: name: push-bot push: branches: [main] @@ -51,15 +51,15 @@ tools: github: allowed: [list_issues] ---`, - filename: "alias-with-push.md", + filename: "command-with-push.md", expectedSimpleCondition: false, expectedEventAware: true, }, { - name: "alias with schedule should use event-aware condition", + name: "command with schedule should use event-aware condition", frontmatter: `--- on: - alias: + command: name: schedule-bot schedule: - cron: "0 9 * * 1" @@ -67,7 +67,7 @@ tools: github: allowed: [list_issues] ---`, - filename: "alias-with-schedule.md", + filename: "command-with-schedule.md", expectedSimpleCondition: false, expectedEventAware: true, }, @@ -77,9 +77,9 @@ tools: t.Run(tt.name, func(t *testing.T) { testContent := tt.frontmatter + ` -# Test Event-Aware Alias Conditions +# Test Event-Aware Command Conditions -This test validates that alias conditions are applied correctly based on event types. +This test validates that command conditions are applied correctly based on event types. ` testFile := filepath.Join(tmpDir, tt.filename) @@ -103,24 +103,24 @@ This test validates that alias conditions are applied correctly based on event t lockContentStr := string(lockContent) if tt.expectedSimpleCondition { - // Should contain simple alias condition (no complex event_name logic in main job) - expectedPattern := "contains(github.event.issue.body, '@" + // Should contain simple command condition (no complex event_name logic in main job) + expectedPattern := "contains(github.event.issue.body, '/" if !strings.Contains(lockContentStr, expectedPattern) { - t.Errorf("Expected simple alias condition containing '%s' but not found", expectedPattern) + t.Errorf("Expected simple command condition containing '%s' but not found", expectedPattern) } - // For simple alias workflows, the main job condition should not contain github.event_name logic + // For simple command workflows, the main job condition should not contain github.event_name logic // We can check this by looking for any line with "if:" that contains both "contains(" and NOT "github.event_name" lines := strings.Split(lockContentStr, "\n") - foundSimpleAliasCondition := false + foundSimpleCommandCondition := false for _, line := range lines { if strings.Contains(line, "if:") && strings.Contains(line, "contains(") && !strings.Contains(line, "github.event_name") { - foundSimpleAliasCondition = true + foundSimpleCommandCondition = true break } } - if !foundSimpleAliasCondition { - t.Errorf("Expected to find simple alias condition (contains without github.event_name) but not found") + if !foundSimpleCommandCondition { + t.Errorf("Expected to find simple command condition (contains without github.event_name) but not found") } } diff --git a/pkg/workflow/compiler.go b/pkg/workflow/compiler.go index d8e4633219..79625e5716 100644 --- a/pkg/workflow/compiler.go +++ b/pkg/workflow/compiler.go @@ -114,30 +114,30 @@ func NewCompilerWithCustomOutput(verbose bool, engineOverride string, customOutp // WorkflowData holds all the data needed to generate a GitHub Actions workflow type WorkflowData struct { - Name string - On string - Permissions string - Concurrency string - RunName string - Env string - If string - TimeoutMinutes string - CustomSteps string - PostSteps string // steps to run after AI execution - RunsOn string - Tools map[string]any - MarkdownContent string - AllowedTools string - AI string // "claude" or "codex" (for backwards compatibility) - EngineConfig *EngineConfig // Extended engine configuration - StopTime string - Alias string // for @alias trigger support - AliasOtherEvents map[string]any // for merging alias with other events - AIReaction string // AI reaction type like "eyes", "heart", etc. - Jobs map[string]any // custom job configurations with dependencies - Cache string // cache configuration - NeedsTextOutput bool // whether the workflow uses ${{ needs.task.outputs.text }} - SafeOutputs *SafeOutputsConfig // output configuration for automatic output routes + Name string + On string + Permissions string + Concurrency string + RunName string + Env string + If string + TimeoutMinutes string + CustomSteps string + PostSteps string // steps to run after AI execution + RunsOn string + Tools map[string]any + MarkdownContent string + AllowedTools string + AI string // "claude" or "codex" (for backwards compatibility) + EngineConfig *EngineConfig // Extended engine configuration + StopTime string + Command string // for /command trigger support + CommandOtherEvents map[string]any // for merging command with other events + AIReaction string // AI reaction type like "eyes", "heart", etc. + Jobs map[string]any // custom job configurations with dependencies + Cache string // cache configuration + NeedsTextOutput bool // whether the workflow uses ${{ needs.task.outputs.text }} + SafeOutputs *SafeOutputsConfig // output configuration for automatic output routes } // SafeOutputsConfig holds configuration for automatic output routes @@ -609,20 +609,20 @@ func (c *Compiler) parseWorkflowFile(markdownPath string) (*WorkflowData, error) } } - workflowData.Alias = c.extractAliasName(result.Frontmatter) + workflowData.Command = c.extractCommandName(result.Frontmatter) workflowData.Jobs = c.extractJobsFromFrontmatter(result.Frontmatter) // Parse output configuration workflowData.SafeOutputs = c.extractSafeOutputsConfig(result.Frontmatter) - // Check if "alias" is used as a trigger in the "on" section + // Check if "command" is used as a trigger in the "on" section // Also extract "reaction" from the "on" section - var hasAlias bool + var hasCommand bool var hasReaction bool var hasStopAfter bool var otherEvents map[string]any if onValue, exists := result.Frontmatter["on"]; exists { - // Check for new format: on.alias and on.reaction + // Check for new format: on.command and on.reaction if onMap, ok := onValue.(map[string]any); ok { // Check for stop-after in the on section if _, hasStopAfterKey := onMap["stop-after"]; hasStopAfterKey { @@ -637,39 +637,39 @@ func (c *Compiler) parseWorkflowFile(markdownPath string) (*WorkflowData, error) } } - if _, hasAliasKey := onMap["alias"]; hasAliasKey { - hasAlias = true - // Set default alias to filename if not specified in the alias section - if workflowData.Alias == "" { + if _, hasCommandKey := onMap["command"]; hasCommandKey { + hasCommand = true + // Set default command to filename if not specified in the command section + if workflowData.Command == "" { baseName := strings.TrimSuffix(filepath.Base(markdownPath), ".md") - workflowData.Alias = baseName + workflowData.Command = baseName } // Check for conflicting events conflictingEvents := []string{"issues", "issue_comment", "pull_request", "pull_request_review_comment"} for _, eventName := range conflictingEvents { if _, hasConflict := onMap[eventName]; hasConflict { - return nil, fmt.Errorf("cannot use 'alias' with '%s' in the same workflow", eventName) + return nil, fmt.Errorf("cannot use 'command' with '%s' in the same workflow", eventName) } } - // Clear the On field so applyDefaults will handle alias trigger generation + // Clear the On field so applyDefaults will handle command trigger generation workflowData.On = "" } - // Extract other (non-conflicting) events excluding alias, reaction, and stop-after - otherEvents = filterMapKeys(onMap, "alias", "reaction", "stop-after") + // Extract other (non-conflicting) events excluding command, reaction, and stop-after + otherEvents = filterMapKeys(onMap, "command", "reaction", "stop-after") } } - // Clear alias field if no alias trigger was found - if !hasAlias { - workflowData.Alias = "" + // Clear command field if no command trigger was found + if !hasCommand { + workflowData.Command = "" } // Store other events for merging in applyDefaults - if hasAlias && len(otherEvents) > 0 { + if hasCommand && len(otherEvents) > 0 { // We'll store this and handle it in applyDefaults - workflowData.On = "" // This will trigger alias handling in applyDefaults - workflowData.AliasOtherEvents = otherEvents + workflowData.On = "" // This will trigger command handling in applyDefaults + workflowData.CommandOtherEvents = otherEvents } else if (hasReaction || hasStopAfter) && len(otherEvents) > 0 { // Only re-marshal the "on" if we have to onEventsYAML, err := yaml.Marshal(map[string]any{"on": otherEvents}) @@ -863,14 +863,14 @@ func (c *Compiler) generateJobName(workflowName string) string { } return jobName -} // extractAliasName extracts the alias name from frontmatter using the new nested format -func (c *Compiler) extractAliasName(frontmatter map[string]any) string { - // Check new format: on.alias.name +} // extractCommandName extracts the command name from frontmatter using the new nested format +func (c *Compiler) extractCommandName(frontmatter map[string]any) string { + // Check new format: on.command.name if onValue, exists := frontmatter["on"]; exists { if onMap, ok := onValue.(map[string]any); ok { - if aliasValue, hasAlias := onMap["alias"]; hasAlias { - if aliasMap, ok := aliasValue.(map[string]any); ok { - if nameValue, hasName := aliasMap["name"]; hasName { + if commandValue, hasCommand := onMap["command"]; hasCommand { + if commandMap, ok := commandValue.(map[string]any); ok { + if nameValue, hasName := commandMap["name"]; hasName { if nameStr, ok := nameValue.(string); ok { return nameStr } @@ -885,19 +885,19 @@ func (c *Compiler) extractAliasName(frontmatter map[string]any) string { // applyDefaults applies default values for missing workflow sections func (c *Compiler) applyDefaults(data *WorkflowData, markdownPath string) { - // Check if this is an alias trigger workflow (by checking if user specified "on.alias") - isAliasTrigger := false + // Check if this is a command trigger workflow (by checking if user specified "on.command") + isCommandTrigger := false if data.On == "" { - // Check the original frontmatter for alias trigger + // Check the original frontmatter for command trigger content, err := os.ReadFile(markdownPath) if err == nil { result, err := parser.ExtractFrontmatterFromContent(string(content)) if err == nil { if onValue, exists := result.Frontmatter["on"]; exists { - // Check for new format: on.alias + // Check for new format: on.command if onMap, ok := onValue.(map[string]any); ok { - if _, hasAlias := onMap["alias"]; hasAlias { - isAliasTrigger = true + if _, hasCommand := onMap["command"]; hasCommand { + isCommandTrigger = true } } } @@ -906,9 +906,9 @@ func (c *Compiler) applyDefaults(data *WorkflowData, markdownPath string) { } if data.On == "" { - if isAliasTrigger { - // Generate alias-specific GitHub Actions events (updated to include reopened and pull_request) - aliasEvents := `on: + if isCommandTrigger { + // Generate command-specific GitHub Actions events (updated to include reopened and pull_request) + commandEvents := `on: issues: types: [opened, edited, reopened] issue_comment: @@ -919,9 +919,9 @@ func (c *Compiler) applyDefaults(data *WorkflowData, markdownPath string) { types: [created, edited]` // Check if there are other events to merge - if len(data.AliasOtherEvents) > 0 { - // Merge alias events with other events - aliasEventsMap := map[string]any{ + if len(data.CommandOtherEvents) > 0 { + // Merge command events with other events + commandEventsMap := map[string]any{ "issues": map[string]any{ "types": []string{"opened", "edited", "reopened"}, }, @@ -936,30 +936,30 @@ func (c *Compiler) applyDefaults(data *WorkflowData, markdownPath string) { }, } - // Merge other events into alias events - for key, value := range data.AliasOtherEvents { - aliasEventsMap[key] = value + // Merge other events into command events + for key, value := range data.CommandOtherEvents { + commandEventsMap[key] = value } // Convert merged events to YAML - mergedEventsYAML, err := yaml.Marshal(map[string]any{"on": aliasEventsMap}) + mergedEventsYAML, err := yaml.Marshal(map[string]any{"on": commandEventsMap}) if err == nil { data.On = strings.TrimSuffix(string(mergedEventsYAML), "\n") } else { - // If conversion fails, just use alias events - data.On = aliasEvents + // If conversion fails, just use command events + data.On = commandEvents } } else { - data.On = aliasEvents + data.On = commandEvents } - // Add conditional logic to check for alias in issue content - // Use event-aware condition that only applies alias checks to comment-related events - hasOtherEvents := len(data.AliasOtherEvents) > 0 - aliasConditionTree := buildEventAwareAliasCondition(data.Alias, hasOtherEvents) + // Add conditional logic to check for command in issue content + // Use event-aware condition that only applies command checks to comment-related events + hasOtherEvents := len(data.CommandOtherEvents) > 0 + commandConditionTree := buildEventAwareCommandCondition(data.Command, hasOtherEvents) if data.If == "" { - data.If = fmt.Sprintf("if: %s", aliasConditionTree.Render()) + data.If = fmt.Sprintf("if: %s", commandConditionTree.Render()) } } else { data.On = `on: @@ -986,7 +986,7 @@ func (c *Compiler) applyDefaults(data *WorkflowData, markdownPath string) { } // Generate concurrency configuration using the dedicated concurrency module - data.Concurrency = GenerateConcurrencyConfig(data, isAliasTrigger) + data.Concurrency = GenerateConcurrencyConfig(data, isCommandTrigger) if data.RunName == "" { data.RunName = fmt.Sprintf(`run-name: "%s"`, data.Name) @@ -1504,10 +1504,10 @@ func (c *Compiler) generateYAML(data *WorkflowData) (string, error) { // isTaskJobNeeded determines if the task job is required func (c *Compiler) isTaskJobNeeded(data *WorkflowData) bool { // Task job is needed if: - // 1. Alias is configured (for team member checking) + // 1. Command is configured (for team member checking) // 2. Text output is needed (for compute-text action) // 3. If condition is specified (to handle runtime conditions) - return data.Alias != "" || data.NeedsTextOutput || data.If != "" + return data.Command != "" || data.NeedsTextOutput || data.If != "" } // buildJobs creates all jobs for the workflow and adds them to the job manager @@ -1606,24 +1606,24 @@ func (c *Compiler) buildTaskJob(data *WorkflowData) (*Job, error) { outputs := map[string]string{} var steps []string - // Add team member check for alias workflows, but only when triggered by alias mention - if data.Alias != "" { - // Build condition that only applies to alias mentions in comment-related events - aliasCondition := buildAliasOnlyCondition(data.Alias) - aliasConditionStr := aliasCondition.Render() + // Add team member check for command workflows, but only when triggered by command mention + if data.Command != "" { + // Build condition that only applies to command mentions in comment-related events + commandCondition := buildCommandOnlyCondition(data.Command) + commandConditionStr := commandCondition.Render() // Build the validation condition using expression nodes - // Since the check-team-member step is gated by alias condition, we check if it ran and returned 'false' - // This avoids running validation when the step didn't run at all (non-alias triggers) + // Since the check-team-member step is gated by command condition, we check if it ran and returned 'false' + // This avoids running validation when the step didn't run at all (non-command triggers) validationCondition := BuildEquals( BuildPropertyAccess("steps.check-team-member.outputs.is_team_member"), BuildStringLiteral("false"), ) validationConditionStr := validationCondition.Render() - steps = append(steps, " - name: Check team membership for alias workflow\n") + steps = append(steps, " - name: Check team membership for command workflow\n") steps = append(steps, " id: check-team-member\n") - steps = append(steps, fmt.Sprintf(" if: %s\n", aliasConditionStr)) + steps = append(steps, fmt.Sprintf(" if: %s\n", commandConditionStr)) steps = append(steps, " uses: actions/github-script@v7\n") steps = append(steps, " with:\n") steps = append(steps, " script: |\n") @@ -1638,7 +1638,7 @@ func (c *Compiler) buildTaskJob(data *WorkflowData) (*Job, error) { steps = append(steps, " - name: Validate team membership\n") steps = append(steps, fmt.Sprintf(" if: %s\n", validationConditionStr)) steps = append(steps, " run: |\n") - steps = append(steps, " echo \"❌ Access denied: Only team members can trigger alias workflows\"\n") + steps = append(steps, " echo \"❌ Access denied: Only team members can trigger command workflows\"\n") steps = append(steps, " echo \"User ${{ github.actor }} is not a team member\"\n") steps = append(steps, " exit 1\n") } @@ -1689,8 +1689,8 @@ func (c *Compiler) buildAddReactionJob(data *WorkflowData, taskJobCreated bool) // Add environment variables steps = append(steps, " env:\n") steps = append(steps, fmt.Sprintf(" GITHUB_AW_REACTION: %s\n", data.AIReaction)) - if data.Alias != "" { - steps = append(steps, fmt.Sprintf(" GITHUB_AW_ALIAS: %s\n", data.Alias)) + if data.Command != "" { + steps = append(steps, fmt.Sprintf(" GITHUB_AW_COMMAND: %s\n", data.Command)) } steps = append(steps, " with:\n") diff --git a/pkg/workflow/compiler_test.go b/pkg/workflow/compiler_test.go index c4adaf799c..8dc2bd5cbf 100644 --- a/pkg/workflow/compiler_test.go +++ b/pkg/workflow/compiler_test.go @@ -602,9 +602,9 @@ This is a test workflow. } } -func TestAliasSection(t *testing.T) { +func TestCommandSection(t *testing.T) { // Create temporary directory for test files - tmpDir, err := os.MkdirTemp("", "workflow-alias-test") + tmpDir, err := os.MkdirTemp("", "workflow-command-test") if err != nil { t.Fatal(err) } @@ -613,55 +613,56 @@ func TestAliasSection(t *testing.T) { compiler := NewCompiler(false, "", "test") tests := []struct { - name string - frontmatter string - filename string - expectedOn string - expectedIf string - expectedAlias string + name string + frontmatter string + filename string + expectedOn string + expectedIf string + expectedCommand string }{ { - name: "alias trigger", + name: "command trigger", frontmatter: `--- on: - alias: + command: + name: test-bot tools: github: allowed: [list_issues] ---`, - filename: "test-bot.md", - expectedOn: "on:\n issues:\n types: [opened, edited, reopened]\n issue_comment:\n types: [created, edited]\n pull_request:\n types: [opened, edited, reopened]", - expectedIf: "if: ((contains(github.event.issue.body, '@test-bot')) || (contains(github.event.comment.body, '@test-bot'))) || (contains(github.event.pull_request.body, '@test-bot'))", - expectedAlias: "test-bot", + filename: "test-bot.md", + expectedOn: "on:\n issues:\n types: [opened, edited, reopened]\n issue_comment:\n types: [created, edited]\n pull_request:\n types: [opened, edited, reopened]", + expectedIf: "if: ((contains(github.event.issue.body, '/test-bot')) || (contains(github.event.comment.body, '/test-bot'))) || (contains(github.event.pull_request.body, '/test-bot'))", + expectedCommand: "test-bot", }, { - name: "new format alias trigger", + name: "new format command trigger", frontmatter: `--- on: - alias: + command: name: new-bot tools: github: allowed: [list_issues] ---`, - filename: "test-new-format.md", - expectedOn: "on:\n issues:\n types: [opened, edited, reopened]\n issue_comment:\n types: [created, edited]\n pull_request:\n types: [opened, edited, reopened]", - expectedIf: "if: ((contains(github.event.issue.body, '@new-bot')) || (contains(github.event.comment.body, '@new-bot'))) || (contains(github.event.pull_request.body, '@new-bot'))", - expectedAlias: "new-bot", + filename: "test-new-format.md", + expectedOn: "on:\n issues:\n types: [opened, edited, reopened]\n issue_comment:\n types: [created, edited]\n pull_request:\n types: [opened, edited, reopened]", + expectedIf: "if: ((contains(github.event.issue.body, '/new-bot')) || (contains(github.event.comment.body, '/new-bot'))) || (contains(github.event.pull_request.body, '/new-bot'))", + expectedCommand: "new-bot", }, { - name: "new format alias trigger no name defaults to filename", + name: "new format command trigger no name defaults to filename", frontmatter: `--- on: - alias: {} + command: {} tools: github: allowed: [list_issues] ---`, - filename: "default-name-bot.md", - expectedOn: "on:\n issues:\n types: [opened, edited, reopened]\n issue_comment:\n types: [created, edited]\n pull_request:\n types: [opened, edited, reopened]", - expectedIf: "if: ((contains(github.event.issue.body, '@default-name-bot')) || (contains(github.event.comment.body, '@default-name-bot'))) || (contains(github.event.pull_request.body, '@default-name-bot'))", - expectedAlias: "default-name-bot", + filename: "default-name-bot.md", + expectedOn: "on:\n issues:\n types: [opened, edited, reopened]\n issue_comment:\n types: [created, edited]\n pull_request:\n types: [opened, edited, reopened]", + expectedIf: "if: ((contains(github.event.issue.body, '/default-name-bot')) || (contains(github.event.comment.body, '/default-name-bot'))) || (contains(github.event.pull_request.body, '/default-name-bot'))", + expectedCommand: "default-name-bot", }, } @@ -669,9 +670,9 @@ tools: t.Run(tt.name, func(t *testing.T) { testContent := tt.frontmatter + ` -# Test Alias Workflow +# Test Command Workflow -This is a test workflow for alias triggering. +This is a test workflow for command triggering. ` testFile := filepath.Join(tmpDir, tt.filename) @@ -704,14 +705,14 @@ This is a test workflow for alias triggering. t.Errorf("Expected lock file to contain '%s' but it didn't.\nContent:\n%s", tt.expectedIf, lockContent) } - // The alias is validated during compilation and should be present in the if condition + // The command is validated during compilation and should be present in the if condition }) } } -func TestAliasWithOtherEvents(t *testing.T) { +func TestCommandWithOtherEvents(t *testing.T) { // Create temporary directory for test files - tmpDir, err := os.MkdirTemp("", "workflow-alias-merge-test") + tmpDir, err := os.MkdirTemp("", "workflow-command-merge-test") if err != nil { t.Fatal(err) } @@ -725,32 +726,32 @@ func TestAliasWithOtherEvents(t *testing.T) { filename string expectedOn string expectedIf string - expectedAlias string + expectedCommand string shouldError bool expectedErrorMsg string }{ { - name: "alias with workflow_dispatch", + name: "command with workflow_dispatch", frontmatter: `--- on: - alias: + command: name: test-bot workflow_dispatch: tools: github: allowed: [list_issues] ---`, - filename: "alias-with-dispatch.md", - expectedOn: "\"on\":\n issue_comment:\n types:\n - created\n - edited\n issues:\n types:\n - opened\n - edited\n - reopened\n pull_request:\n types:\n - opened\n - edited\n - reopened\n pull_request_review_comment:\n types:\n - created\n - edited\n workflow_dispatch: null", - expectedIf: "if: ((github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request' || github.event_name == 'pull_request_review_comment') && (((contains(github.event.issue.body, '@test-bot')) || (contains(github.event.comment.body, '@test-bot'))) || (contains(github.event.pull_request.body, '@test-bot')))) || (!(github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request' || github.event_name == 'pull_request_review_comment'))", - expectedAlias: "test-bot", - shouldError: false, + filename: "command-with-dispatch.md", + expectedOn: "\"on\":\n issue_comment:\n types:\n - created\n - edited\n issues:\n types:\n - opened\n - edited\n - reopened\n pull_request:\n types:\n - opened\n - edited\n - reopened\n pull_request_review_comment:\n types:\n - created\n - edited\n workflow_dispatch: null", + expectedIf: "if: ((github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request' || github.event_name == 'pull_request_review_comment') && (((contains(github.event.issue.body, '/test-bot')) || (contains(github.event.comment.body, '/test-bot'))) || (contains(github.event.pull_request.body, '/test-bot')))) || (!(github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request' || github.event_name == 'pull_request_review_comment'))", + expectedCommand: "test-bot", + shouldError: false, }, { - name: "alias with schedule", + name: "command with schedule", frontmatter: `--- on: - alias: + command: name: schedule-bot schedule: - cron: "0 9 * * 1" @@ -758,17 +759,17 @@ tools: github: allowed: [list_issues] ---`, - filename: "alias-with-schedule.md", - expectedOn: "\"on\":\n issue_comment:\n types:\n - created\n - edited\n issues:\n types:\n - opened\n - edited\n - reopened\n pull_request:\n types:\n - opened\n - edited\n - reopened\n pull_request_review_comment:\n types:\n - created\n - edited\n schedule:\n - cron: 0 9 * * 1", - expectedIf: "if: ((github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request' || github.event_name == 'pull_request_review_comment') && (((contains(github.event.issue.body, '@schedule-bot')) || (contains(github.event.comment.body, '@schedule-bot'))) || (contains(github.event.pull_request.body, '@schedule-bot')))) || (!(github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request' || github.event_name == 'pull_request_review_comment'))", - expectedAlias: "schedule-bot", - shouldError: false, + filename: "command-with-schedule.md", + expectedOn: "\"on\":\n issue_comment:\n types:\n - created\n - edited\n issues:\n types:\n - opened\n - edited\n - reopened\n pull_request:\n types:\n - opened\n - edited\n - reopened\n pull_request_review_comment:\n types:\n - created\n - edited\n schedule:\n - cron: 0 9 * * 1", + expectedIf: "if: ((github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request' || github.event_name == 'pull_request_review_comment') && (((contains(github.event.issue.body, '/schedule-bot')) || (contains(github.event.comment.body, '/schedule-bot'))) || (contains(github.event.pull_request.body, '/schedule-bot')))) || (!(github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request' || github.event_name == 'pull_request_review_comment'))", + expectedCommand: "schedule-bot", + shouldError: false, }, { - name: "alias with multiple compatible events", + name: "command with multiple compatible events", frontmatter: `--- on: - alias: + command: name: multi-bot workflow_dispatch: push: @@ -777,17 +778,17 @@ tools: github: allowed: [list_issues] ---`, - filename: "alias-with-multiple.md", - expectedOn: "\"on\":\n issue_comment:\n types:\n - created\n - edited\n issues:\n types:\n - opened\n - edited\n - reopened\n pull_request:\n types:\n - opened\n - edited\n - reopened\n pull_request_review_comment:\n types:\n - created\n - edited\n push:\n branches:\n - main\n workflow_dispatch: null", - expectedIf: "if: ((github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request' || github.event_name == 'pull_request_review_comment') && (((contains(github.event.issue.body, '@multi-bot')) || (contains(github.event.comment.body, '@multi-bot'))) || (contains(github.event.pull_request.body, '@multi-bot')))) || (!(github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request' || github.event_name == 'pull_request_review_comment'))", - expectedAlias: "multi-bot", - shouldError: false, + filename: "command-with-multiple.md", + expectedOn: "\"on\":\n issue_comment:\n types:\n - created\n - edited\n issues:\n types:\n - opened\n - edited\n - reopened\n pull_request:\n types:\n - opened\n - edited\n - reopened\n pull_request_review_comment:\n types:\n - created\n - edited\n push:\n branches:\n - main\n workflow_dispatch: null", + expectedIf: "if: ((github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request' || github.event_name == 'pull_request_review_comment') && (((contains(github.event.issue.body, '/multi-bot')) || (contains(github.event.comment.body, '/multi-bot'))) || (contains(github.event.pull_request.body, '/multi-bot')))) || (!(github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request' || github.event_name == 'pull_request_review_comment'))", + expectedCommand: "multi-bot", + shouldError: false, }, { - name: "alias with conflicting issues event - should error", + name: "command with conflicting issues event - should error", frontmatter: `--- on: - alias: + command: name: conflict-bot issues: types: [closed] @@ -795,15 +796,15 @@ tools: github: allowed: [list_issues] ---`, - filename: "alias-with-issues.md", + filename: "command-with-issues.md", shouldError: true, - expectedErrorMsg: "cannot use 'alias' with 'issues'", + expectedErrorMsg: "cannot use 'command' with 'issues' in the same workflow", }, { - name: "alias with conflicting issue_comment event - should error", + name: "command with conflicting issue_comment event - should error", frontmatter: `--- on: - alias: + command: name: conflict-bot issue_comment: types: [deleted] @@ -811,15 +812,15 @@ tools: github: allowed: [list_issues] ---`, - filename: "alias-with-issue-comment.md", + filename: "command-with-issue-comment.md", shouldError: true, - expectedErrorMsg: "cannot use 'alias' with 'issue_comment'", + expectedErrorMsg: "cannot use 'command' with 'issue_comment'", }, { - name: "alias with conflicting pull_request event - should error", + name: "command with conflicting pull_request event - should error", frontmatter: `--- on: - alias: + command: name: conflict-bot pull_request: types: [closed] @@ -827,15 +828,15 @@ tools: github: allowed: [list_issues] ---`, - filename: "alias-with-pull-request.md", + filename: "command-with-pull-request.md", shouldError: true, - expectedErrorMsg: "cannot use 'alias' with 'pull_request'", + expectedErrorMsg: "cannot use 'command' with 'pull_request'", }, { - name: "alias with conflicting pull_request_review_comment event - should error", + name: "command with conflicting pull_request_review_comment event - should error", frontmatter: `--- on: - alias: + command: name: conflict-bot pull_request_review_comment: types: [created] @@ -843,9 +844,9 @@ tools: github: allowed: [list_issues] ---`, - filename: "alias-with-pull-request-review-comment.md", + filename: "command-with-pull-request-review-comment.md", shouldError: true, - expectedErrorMsg: "cannot use 'alias' with 'pull_request_review_comment'", + expectedErrorMsg: "cannot use 'command' with 'pull_request_review_comment'", }, } @@ -853,9 +854,9 @@ tools: t.Run(tt.name, func(t *testing.T) { testContent := tt.frontmatter + ` -# Test Alias with Other Events Workflow +# Test Command with Other Events Workflow -This is a test workflow for alias merging with other events. +This is a test workflow for command merging with other events. ` testFile := filepath.Join(tmpDir, tt.filename) @@ -3492,8 +3493,6 @@ Test workflow with reaction and comment editing. "editCommentWithWorkflowLink", // This should be in the new script "runUrl =", // This should be in the new script for workflow run URL "Comment update endpoint", // This should be logged in the new script - "GITHUB_AW_ALIAS", // This should check for alias environment variable - "shouldEditComment = alias", // This should conditionally edit based on alias } for _, expected := range expectedStrings { @@ -3513,19 +3512,19 @@ Test workflow with reaction and comment editing. } } -// TestAliasReactionWithCommentEdit tests alias workflows with reaction and comment editing -func TestAliasReactionWithCommentEdit(t *testing.T) { +// TestCommandReactionWithCommentEdit tests command workflows with reaction and comment editing +func TestCommandReactionWithCommentEdit(t *testing.T) { // Create temporary directory for test files - tmpDir, err := os.MkdirTemp("", "alias-reaction-edit-test") + tmpDir, err := os.MkdirTemp("", "command-reaction-edit-test") if err != nil { t.Fatal(err) } defer os.RemoveAll(tmpDir) - // Create a test markdown file with alias and reaction + // Create a test markdown file with command and reaction testContent := `--- on: - alias: + command: name: test-bot reaction: eyes permissions: @@ -3537,12 +3536,12 @@ tools: allowed: [get_issue] --- -# Alias Bot with Reaction Test +# Command Bot with Reaction Test -Test alias workflow with reaction and comment editing. +Test command workflow with reaction and comment editing. ` - testFile := filepath.Join(tmpDir, "test-alias-bot.md") + testFile := filepath.Join(tmpDir, "test-command-bot.md") if err := os.WriteFile(testFile, []byte(testContent), 0644); err != nil { t.Fatal(err) } @@ -3555,9 +3554,9 @@ Test alias workflow with reaction and comment editing. t.Fatalf("Failed to parse workflow: %v", err) } - // Verify alias and reaction fields are parsed correctly - if workflowData.Alias != "test-bot" { - t.Errorf("Expected Alias to be 'test-bot', got '%s'", workflowData.Alias) + // Verify command and reaction fields are parsed correctly + if workflowData.Command != "test-bot" { + t.Errorf("Expected Command to be 'test-bot', got '%s'", workflowData.Command) } if workflowData.AIReaction != "eyes" { t.Errorf("Expected AIReaction to be 'eyes', got '%s'", workflowData.AIReaction) @@ -3572,7 +3571,7 @@ Test alias workflow with reaction and comment editing. // Check for both environment variables in the generated YAML expectedEnvVars := []string{ "GITHUB_AW_REACTION: eyes", - "GITHUB_AW_ALIAS: test-bot", + "GITHUB_AW_COMMAND: test-bot", } for _, expected := range expectedEnvVars { @@ -5108,10 +5107,10 @@ engine: claude description: "stop-after should be compiled away when used with workflow_dispatch and schedule", }, { - name: "stop-after with alias trigger", + name: "stop-after with command trigger", frontmatter: `--- on: - alias: + command: name: test-bot workflow_dispatch: stop-after: "2024-12-31T23:59:59Z" @@ -5182,10 +5181,10 @@ engine: claude description: "stop-after should be compiled away when used only with schedule", }, { - name: "stop-after with both alias and reaction", + name: "stop-after with both command and reaction", frontmatter: `--- on: - alias: + command: name: test-bot reaction: heart workflow_dispatch: @@ -5239,10 +5238,10 @@ engine: claude description: "stop-after should be compiled away when used with reaction and schedule", }, { - name: "stop-after with alias and schedule", + name: "stop-after with command and schedule", frontmatter: `--- on: - alias: + command: name: scheduler-bot schedule: - cron: "0 12 * * *" diff --git a/pkg/workflow/concurrency.go b/pkg/workflow/concurrency.go index d6a4f705f6..3344a9d9ab 100644 --- a/pkg/workflow/concurrency.go +++ b/pkg/workflow/concurrency.go @@ -7,21 +7,21 @@ import ( // GenerateConcurrencyConfig generates the concurrency configuration for a workflow // based on its trigger types and characteristics. -func GenerateConcurrencyConfig(workflowData *WorkflowData, isAliasTrigger bool) string { +func GenerateConcurrencyConfig(workflowData *WorkflowData, isCommandTrigger bool) string { // Don't override if already set if workflowData.Concurrency != "" { return workflowData.Concurrency } // Build concurrency group keys - keys := buildConcurrencyGroupKeys(workflowData, isAliasTrigger) + keys := buildConcurrencyGroupKeys(workflowData, isCommandTrigger) groupValue := strings.Join(keys, "-") // Build the concurrency configuration concurrencyConfig := fmt.Sprintf("concurrency:\n group: \"%s\"", groupValue) // Add cancel-in-progress if appropriate - if shouldEnableCancelInProgress(workflowData, isAliasTrigger) { + if shouldEnableCancelInProgress(workflowData, isCommandTrigger) { concurrencyConfig += "\n cancel-in-progress: true" } @@ -44,11 +44,11 @@ func isDiscussionWorkflow(on string) bool { } // buildConcurrencyGroupKeys builds an array of keys for the concurrency group -func buildConcurrencyGroupKeys(workflowData *WorkflowData, isAliasTrigger bool) []string { +func buildConcurrencyGroupKeys(workflowData *WorkflowData, isCommandTrigger bool) []string { keys := []string{"gh-aw", "${{ github.workflow }}"} - if isAliasTrigger { - // For alias workflows: use issue/PR number + if isCommandTrigger { + // For command workflows: use issue/PR number keys = append(keys, "${{ github.event.issue.number || github.event.pull_request.number }}") } else if isPullRequestWorkflow(workflowData.On) && isIssueWorkflow(workflowData.On) { // Mixed workflows with both issue and PR triggers: use issue/PR number @@ -74,9 +74,9 @@ func buildConcurrencyGroupKeys(workflowData *WorkflowData, isAliasTrigger bool) } // shouldEnableCancelInProgress determines if cancel-in-progress should be enabled -func shouldEnableCancelInProgress(workflowData *WorkflowData, isAliasTrigger bool) bool { - // Never enable cancellation for alias workflows - if isAliasTrigger { +func shouldEnableCancelInProgress(workflowData *WorkflowData, isCommandTrigger bool) bool { + // Never enable cancellation for command workflows + if isCommandTrigger { return false } diff --git a/pkg/workflow/concurrency_test.go b/pkg/workflow/concurrency_test.go index 251e833e3b..683f16b28b 100644 --- a/pkg/workflow/concurrency_test.go +++ b/pkg/workflow/concurrency_test.go @@ -43,16 +43,16 @@ tools: description: "PR workflows should use dynamic concurrency with PR number and cancellation", }, { - name: "alias workflow should have dynamic concurrency without cancel", + name: "command workflow should have dynamic concurrency without cancel", frontmatter: `--- on: - alias: + command: name: test-bot tools: github: allowed: [list_issues] ---`, - filename: "alias-workflow.md", + filename: "command-workflow.md", expectedConcurrency: `concurrency: group: "gh-aw-${{ github.workflow }}-${{ github.event.issue.number || github.event.pull_request.number }}"`, shouldHaveCancel: false, diff --git a/pkg/workflow/team_member_test.go b/pkg/workflow/team_member_test.go index ac9b31a6fb..916964be63 100644 --- a/pkg/workflow/team_member_test.go +++ b/pkg/workflow/team_member_test.go @@ -7,8 +7,8 @@ import ( "testing" ) -// TestTeamMemberCheckForAliasWorkflows tests that team member checks are only added to alias workflows -func TestTeamMemberCheckForAliasWorkflows(t *testing.T) { +// TestTeamMemberCheckForCommandWorkflows tests that team member checks are only added to command workflows +func TestTeamMemberCheckForCommandWorkflows(t *testing.T) { // Create temporary directory for test files tmpDir, err := os.MkdirTemp("", "workflow-team-member-test") if err != nil { @@ -25,10 +25,10 @@ func TestTeamMemberCheckForAliasWorkflows(t *testing.T) { expectTeamMemberCheck bool }{ { - name: "alias workflow should include team member check", + name: "command workflow should include team member check", frontmatter: `--- on: - alias: + command: name: test-bot tools: github: @@ -37,11 +37,11 @@ tools: # Test Bot Test workflow content.`, - filename: "alias-workflow.md", + filename: "command-workflow.md", expectTeamMemberCheck: true, }, { - name: "non-alias workflow should not include team member check", + name: "non-command workflow should not include team member check", frontmatter: `--- on: push: @@ -73,10 +73,10 @@ Test workflow content.`, expectTeamMemberCheck: false, }, { - name: "alias with other events should include team member check", + name: "command with other events should include team member check", frontmatter: `--- on: - alias: + command: name: multi-bot workflow_dispatch: tools: @@ -90,10 +90,10 @@ Test workflow content.`, expectTeamMemberCheck: true, }, { - name: "alias with push events should have conditional team member check", + name: "command with push events should have conditional team member check", frontmatter: `--- on: - alias: + command: name: docs-bot push: branches: [main] @@ -134,31 +134,31 @@ Test workflow content.`, lockContentStr := string(lockContent) // Check for team member check - hasTeamMemberCheck := strings.Contains(lockContentStr, "Check team membership for alias workflow") + hasTeamMemberCheck := strings.Contains(lockContentStr, "Check team membership for command workflow") if tt.expectTeamMemberCheck { if !hasTeamMemberCheck { - t.Errorf("Expected team member check in alias workflow but not found") + t.Errorf("Expected team member check in command workflow but not found") } // Also verify the validation step is present if !strings.Contains(lockContentStr, "Validate team membership") { t.Errorf("Expected team membership validation step but not found") } // Check for the specific failure message - if !strings.Contains(lockContentStr, "Only team members can trigger alias workflows") { + if !strings.Contains(lockContentStr, "Only team members can trigger command workflows") { t.Errorf("Expected team member check failure message but not found") } // Verify that team member check has a conditional that only runs for alias mentions if !strings.Contains(lockContentStr, "if: contains(github.event.issue.body") { t.Errorf("Expected team member check to have alias-only condition but not found") } - // Verify that the condition only checks for alias mentions (not other event types) - aliasConditionCount := strings.Count(lockContentStr, "contains(github.event") - if aliasConditionCount < 2 { // Should have conditions for issue.body, comment.body, etc. - t.Errorf("Expected multiple alias content checks but found %d", aliasConditionCount) + // Verify that the condition only checks for command mentions (not other event types) + commandConditionCount := strings.Count(lockContentStr, "contains(github.event") + if commandConditionCount < 2 { // Should have conditions for issue.body, comment.body, etc. + t.Errorf("Expected multiple command content checks but found %d", commandConditionCount) } // Find the team member check section and ensure it doesn't have github.event_name logic - teamMemberCheckStart := strings.Index(lockContentStr, "Check team membership for alias workflow") + teamMemberCheckStart := strings.Index(lockContentStr, "Check team membership for command workflow") teamMemberCheckEnd := strings.Index(lockContentStr[teamMemberCheckStart:], "Compute current body text") if teamMemberCheckStart != -1 && teamMemberCheckEnd != -1 { teamMemberSection := lockContentStr[teamMemberCheckStart : teamMemberCheckStart+teamMemberCheckEnd] @@ -168,7 +168,7 @@ Test workflow content.`, } } else { if hasTeamMemberCheck { - t.Errorf("Did not expect team member check in non-alias workflow but found it") + t.Errorf("Did not expect team member check in non-command workflow but found it") } } })