From f5beb7fe325b4875c0b3af9645531bdd81dfab82 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Oct 2025 13:24:52 +0000 Subject: [PATCH 1/3] Initial plan From 9cabca7754f9d27e4b8bcb65b23f848f885a0725 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Oct 2025 13:44:55 +0000 Subject: [PATCH 2/3] Merge add_reaction step into activation job Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/brave.lock.yml | 24 ++---- .../workflows/changeset-generator.lock.yml | 24 ++---- .github/workflows/issue-classifier.lock.yml | 24 ++---- .github/workflows/pdf-summary.lock.yml | 24 ++---- .github/workflows/plan.lock.yml | 24 ++---- .github/workflows/poem-bot.lock.yml | 24 ++---- .github/workflows/q.lock.yml | 24 ++---- .github/workflows/scout.lock.yml | 24 ++---- .github/workflows/tidy.lock.yml | 25 +++---- .github/workflows/unbloat-docs.lock.yml | 25 +++---- pkg/workflow/compiler.go | 46 +++++++++--- pkg/workflow/compiler_test.go | 75 ++++++++++++++++--- pkg/workflow/reaction.go | 59 --------------- pkg/workflow/reaction_outputs_test.go | 38 ++++++---- .../task_and_reaction_permissions_test.go | 72 +++++------------- 15 files changed, 223 insertions(+), 309 deletions(-) delete mode 100644 pkg/workflow/reaction.go diff --git a/.github/workflows/brave.lock.yml b/.github/workflows/brave.lock.yml index 1185e3ed516..d2b66c2fe73 100644 --- a/.github/workflows/brave.lock.yml +++ b/.github/workflows/brave.lock.yml @@ -115,7 +115,14 @@ jobs: (needs.check_membership.outputs.is_team_member == 'true') && ((github.event_name == 'issue_comment') && ((contains(github.event.comment.body, '/brave')) && (github.event.issue.pull_request == null))) runs-on: ubuntu-latest + permissions: + discussions: write + issues: write + pull-requests: write outputs: + comment_id: ${{ steps.react.outputs.comment-id }} + comment_url: ${{ steps.react.outputs.comment-url }} + reaction_id: ${{ steps.react.outputs.reaction-id }} text: ${{ steps.compute-text.outputs.text }} steps: - name: Check workflow file timestamps @@ -295,24 +302,9 @@ jobs: core.setOutput("text", sanitizedText); } await main(); - - add_reaction: - needs: activation - if: > - github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment' || - (github.event_name == 'pull_request') && (github.event.pull_request.head.repo.full_name == github.repository) - runs-on: ubuntu-latest - permissions: - discussions: write - issues: write - pull-requests: write - outputs: - comment_id: ${{ steps.react.outputs.comment-id }} - comment_url: ${{ steps.react.outputs.comment-url }} - reaction_id: ${{ steps.react.outputs.reaction-id }} - steps: - name: Add eyes reaction to the triggering item id: react + if: github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment' || (github.event_name == 'pull_request') && (github.event.pull_request.head.repo.full_name == github.repository) uses: actions/github-script@v8 env: GITHUB_AW_REACTION: eyes diff --git a/.github/workflows/changeset-generator.lock.yml b/.github/workflows/changeset-generator.lock.yml index 7ce43323847..b4ed3113b88 100644 --- a/.github/workflows/changeset-generator.lock.yml +++ b/.github/workflows/changeset-generator.lock.yml @@ -113,7 +113,14 @@ jobs: if: > (needs.check_membership.outputs.is_team_member == 'true') && (github.event.pull_request.base.ref == github.event.repository.default_branch) runs-on: ubuntu-latest + permissions: + discussions: write + issues: write + pull-requests: write outputs: + comment_id: ${{ steps.react.outputs.comment-id }} + comment_url: ${{ steps.react.outputs.comment-url }} + reaction_id: ${{ steps.react.outputs.reaction-id }} text: ${{ steps.compute-text.outputs.text }} steps: - name: Check workflow file timestamps @@ -293,24 +300,9 @@ jobs: core.setOutput("text", sanitizedText); } await main(); - - add_reaction: - needs: activation - if: > - github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment' || - (github.event_name == 'pull_request') && (github.event.pull_request.head.repo.full_name == github.repository) - runs-on: ubuntu-latest - permissions: - discussions: write - issues: write - pull-requests: write - outputs: - comment_id: ${{ steps.react.outputs.comment-id }} - comment_url: ${{ steps.react.outputs.comment-url }} - reaction_id: ${{ steps.react.outputs.reaction-id }} - steps: - name: Add rocket reaction to the triggering item id: react + if: github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment' || (github.event_name == 'pull_request') && (github.event.pull_request.head.repo.full_name == github.repository) uses: actions/github-script@v8 env: GITHUB_AW_REACTION: rocket diff --git a/.github/workflows/issue-classifier.lock.yml b/.github/workflows/issue-classifier.lock.yml index de64af7b6d7..84234030af2 100644 --- a/.github/workflows/issue-classifier.lock.yml +++ b/.github/workflows/issue-classifier.lock.yml @@ -114,7 +114,14 @@ jobs: needs: check_membership if: needs.check_membership.outputs.is_team_member == 'true' runs-on: ubuntu-latest + permissions: + discussions: write + issues: write + pull-requests: write outputs: + comment_id: ${{ steps.react.outputs.comment-id }} + comment_url: ${{ steps.react.outputs.comment-url }} + reaction_id: ${{ steps.react.outputs.reaction-id }} text: ${{ steps.compute-text.outputs.text }} steps: - name: Check workflow file timestamps @@ -294,24 +301,9 @@ jobs: core.setOutput("text", sanitizedText); } await main(); - - add_reaction: - needs: activation - if: > - github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment' || - (github.event_name == 'pull_request') && (github.event.pull_request.head.repo.full_name == github.repository) - runs-on: ubuntu-latest - permissions: - discussions: write - issues: write - pull-requests: write - outputs: - comment_id: ${{ steps.react.outputs.comment-id }} - comment_url: ${{ steps.react.outputs.comment-url }} - reaction_id: ${{ steps.react.outputs.reaction-id }} - steps: - name: Add eyes reaction to the triggering item id: react + if: github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment' || (github.event_name == 'pull_request') && (github.event.pull_request.head.repo.full_name == github.repository) uses: actions/github-script@v8 env: GITHUB_AW_REACTION: eyes diff --git a/.github/workflows/pdf-summary.lock.yml b/.github/workflows/pdf-summary.lock.yml index 7530ce7957d..ce954c9a1b4 100644 --- a/.github/workflows/pdf-summary.lock.yml +++ b/.github/workflows/pdf-summary.lock.yml @@ -140,7 +140,14 @@ jobs: (github.event_name == 'issue_comment') && ((contains(github.event.comment.body, '/summarize')) && (github.event.issue.pull_request == null)))) || (!(github.event_name == 'issue_comment' || github.event_name == 'issues'))) runs-on: ubuntu-latest + permissions: + discussions: write + issues: write + pull-requests: write outputs: + comment_id: ${{ steps.react.outputs.comment-id }} + comment_url: ${{ steps.react.outputs.comment-url }} + reaction_id: ${{ steps.react.outputs.reaction-id }} text: ${{ steps.compute-text.outputs.text }} steps: - name: Check workflow file timestamps @@ -320,24 +327,9 @@ jobs: core.setOutput("text", sanitizedText); } await main(); - - add_reaction: - needs: activation - if: > - github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment' || - (github.event_name == 'pull_request') && (github.event.pull_request.head.repo.full_name == github.repository) - runs-on: ubuntu-latest - permissions: - discussions: write - issues: write - pull-requests: write - outputs: - comment_id: ${{ steps.react.outputs.comment-id }} - comment_url: ${{ steps.react.outputs.comment-url }} - reaction_id: ${{ steps.react.outputs.reaction-id }} - steps: - name: Add eyes reaction to the triggering item id: react + if: github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment' || (github.event_name == 'pull_request') && (github.event.pull_request.head.repo.full_name == github.repository) uses: actions/github-script@v8 env: GITHUB_AW_REACTION: eyes diff --git a/.github/workflows/plan.lock.yml b/.github/workflows/plan.lock.yml index 6c1eb389770..99aea6517c5 100644 --- a/.github/workflows/plan.lock.yml +++ b/.github/workflows/plan.lock.yml @@ -115,7 +115,14 @@ jobs: (needs.check_membership.outputs.is_team_member == 'true') && ((github.event_name == 'issue_comment') && ((contains(github.event.comment.body, '/plan')) && (github.event.issue.pull_request == null))) runs-on: ubuntu-latest + permissions: + discussions: write + issues: write + pull-requests: write outputs: + comment_id: ${{ steps.react.outputs.comment-id }} + comment_url: ${{ steps.react.outputs.comment-url }} + reaction_id: ${{ steps.react.outputs.reaction-id }} text: ${{ steps.compute-text.outputs.text }} steps: - name: Check workflow file timestamps @@ -295,24 +302,9 @@ jobs: core.setOutput("text", sanitizedText); } await main(); - - add_reaction: - needs: activation - if: > - github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment' || - (github.event_name == 'pull_request') && (github.event.pull_request.head.repo.full_name == github.repository) - runs-on: ubuntu-latest - permissions: - discussions: write - issues: write - pull-requests: write - outputs: - comment_id: ${{ steps.react.outputs.comment-id }} - comment_url: ${{ steps.react.outputs.comment-url }} - reaction_id: ${{ steps.react.outputs.reaction-id }} - steps: - name: Add eyes reaction to the triggering item id: react + if: github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment' || (github.event_name == 'pull_request') && (github.event.pull_request.head.repo.full_name == github.repository) uses: actions/github-script@v8 env: GITHUB_AW_REACTION: eyes diff --git a/.github/workflows/poem-bot.lock.yml b/.github/workflows/poem-bot.lock.yml index 872048d4567..b431b18b06b 100644 --- a/.github/workflows/poem-bot.lock.yml +++ b/.github/workflows/poem-bot.lock.yml @@ -123,7 +123,14 @@ jobs: (needs.check_membership.outputs.is_team_member == 'true') && (((github.event_name == 'issues') && ((github.event_name == 'issues') && (contains(github.event.issue.body, '/poem-bot')))) || (!(github.event_name == 'issues'))) runs-on: ubuntu-latest + permissions: + discussions: write + issues: write + pull-requests: write outputs: + comment_id: ${{ steps.react.outputs.comment-id }} + comment_url: ${{ steps.react.outputs.comment-url }} + reaction_id: ${{ steps.react.outputs.reaction-id }} text: ${{ steps.compute-text.outputs.text }} steps: - name: Check workflow file timestamps @@ -303,24 +310,9 @@ jobs: core.setOutput("text", sanitizedText); } await main(); - - add_reaction: - needs: activation - if: > - github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment' || - (github.event_name == 'pull_request') && (github.event.pull_request.head.repo.full_name == github.repository) - runs-on: ubuntu-latest - permissions: - discussions: write - issues: write - pull-requests: write - outputs: - comment_id: ${{ steps.react.outputs.comment-id }} - comment_url: ${{ steps.react.outputs.comment-url }} - reaction_id: ${{ steps.react.outputs.reaction-id }} - steps: - name: Add eyes reaction to the triggering item id: react + if: github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment' || (github.event_name == 'pull_request') && (github.event.pull_request.head.repo.full_name == github.repository) uses: actions/github-script@v8 env: GITHUB_AW_REACTION: eyes diff --git a/.github/workflows/q.lock.yml b/.github/workflows/q.lock.yml index bbc14212468..fdcef71c6fa 100644 --- a/.github/workflows/q.lock.yml +++ b/.github/workflows/q.lock.yml @@ -158,7 +158,14 @@ jobs: (contains(github.event.discussion.body, '/q')) || (github.event_name == 'discussion_comment') && (contains(github.event.comment.body, '/q'))) runs-on: ubuntu-latest + permissions: + discussions: write + issues: write + pull-requests: write outputs: + comment_id: ${{ steps.react.outputs.comment-id }} + comment_url: ${{ steps.react.outputs.comment-url }} + reaction_id: ${{ steps.react.outputs.reaction-id }} text: ${{ steps.compute-text.outputs.text }} steps: - name: Check workflow file timestamps @@ -338,24 +345,9 @@ jobs: core.setOutput("text", sanitizedText); } await main(); - - add_reaction: - needs: activation - if: > - github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment' || - (github.event_name == 'pull_request') && (github.event.pull_request.head.repo.full_name == github.repository) - runs-on: ubuntu-latest - permissions: - discussions: write - issues: write - pull-requests: write - outputs: - comment_id: ${{ steps.react.outputs.comment-id }} - comment_url: ${{ steps.react.outputs.comment-url }} - reaction_id: ${{ steps.react.outputs.reaction-id }} - steps: - name: Add rocket reaction to the triggering item id: react + if: github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment' || (github.event_name == 'pull_request') && (github.event.pull_request.head.repo.full_name == github.repository) uses: actions/github-script@v8 env: GITHUB_AW_REACTION: rocket diff --git a/.github/workflows/scout.lock.yml b/.github/workflows/scout.lock.yml index 67a38dda28b..18fbd86d4f8 100644 --- a/.github/workflows/scout.lock.yml +++ b/.github/workflows/scout.lock.yml @@ -173,7 +173,14 @@ jobs: (!(github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request' || github.event_name == 'pull_request_review_comment' || github.event_name == 'discussion' || github.event_name == 'discussion_comment'))) runs-on: ubuntu-latest + permissions: + discussions: write + issues: write + pull-requests: write outputs: + comment_id: ${{ steps.react.outputs.comment-id }} + comment_url: ${{ steps.react.outputs.comment-url }} + reaction_id: ${{ steps.react.outputs.reaction-id }} text: ${{ steps.compute-text.outputs.text }} steps: - name: Check workflow file timestamps @@ -353,24 +360,9 @@ jobs: core.setOutput("text", sanitizedText); } await main(); - - add_reaction: - needs: activation - if: > - github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment' || - (github.event_name == 'pull_request') && (github.event.pull_request.head.repo.full_name == github.repository) - runs-on: ubuntu-latest - permissions: - discussions: write - issues: write - pull-requests: write - outputs: - comment_id: ${{ steps.react.outputs.comment-id }} - comment_url: ${{ steps.react.outputs.comment-url }} - reaction_id: ${{ steps.react.outputs.reaction-id }} - steps: - name: Add eyes reaction to the triggering item id: react + if: github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment' || (github.event_name == 'pull_request') && (github.event.pull_request.head.repo.full_name == github.repository) uses: actions/github-script@v8 env: GITHUB_AW_REACTION: eyes diff --git a/.github/workflows/tidy.lock.yml b/.github/workflows/tidy.lock.yml index f57a99ad544..f26b50903f1 100644 --- a/.github/workflows/tidy.lock.yml +++ b/.github/workflows/tidy.lock.yml @@ -131,6 +131,14 @@ jobs: ((github.event_name == 'issue_comment') && ((contains(github.event.comment.body, '/tidy')) && (github.event.issue.pull_request != null)))) || (!(github.event_name == 'issue_comment'))) runs-on: ubuntu-latest + permissions: + discussions: write + issues: write + pull-requests: write + outputs: + comment_id: ${{ steps.react.outputs.comment-id }} + comment_url: ${{ steps.react.outputs.comment-url }} + reaction_id: ${{ steps.react.outputs.reaction-id }} steps: - name: Check workflow file timestamps run: | @@ -147,24 +155,9 @@ jobs: echo "" >> $GITHUB_STEP_SUMMARY fi fi - - add_reaction: - needs: activation - if: > - github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment' || - (github.event_name == 'pull_request') && (github.event.pull_request.head.repo.full_name == github.repository) - runs-on: ubuntu-latest - permissions: - discussions: write - issues: write - pull-requests: write - outputs: - comment_id: ${{ steps.react.outputs.comment-id }} - comment_url: ${{ steps.react.outputs.comment-url }} - reaction_id: ${{ steps.react.outputs.reaction-id }} - steps: - name: Add eyes reaction to the triggering item id: react + if: github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment' || (github.event_name == 'pull_request') && (github.event.pull_request.head.repo.full_name == github.repository) uses: actions/github-script@v8 env: GITHUB_AW_REACTION: eyes diff --git a/.github/workflows/unbloat-docs.lock.yml b/.github/workflows/unbloat-docs.lock.yml index a5cf434edee..52ebd17d028 100644 --- a/.github/workflows/unbloat-docs.lock.yml +++ b/.github/workflows/unbloat-docs.lock.yml @@ -120,6 +120,14 @@ jobs: ((github.event_name == 'issue_comment') && ((contains(github.event.comment.body, '/unbloat')) && (github.event.issue.pull_request != null)))) || (!(github.event_name == 'issue_comment'))) runs-on: ubuntu-latest + permissions: + discussions: write + issues: write + pull-requests: write + outputs: + comment_id: ${{ steps.react.outputs.comment-id }} + comment_url: ${{ steps.react.outputs.comment-url }} + reaction_id: ${{ steps.react.outputs.reaction-id }} steps: - name: Check workflow file timestamps run: | @@ -136,24 +144,9 @@ jobs: echo "" >> $GITHUB_STEP_SUMMARY fi fi - - add_reaction: - needs: activation - if: > - github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment' || - (github.event_name == 'pull_request') && (github.event.pull_request.head.repo.full_name == github.repository) - runs-on: ubuntu-latest - permissions: - discussions: write - issues: write - pull-requests: write - outputs: - comment_id: ${{ steps.react.outputs.comment-id }} - comment_url: ${{ steps.react.outputs.comment-url }} - reaction_id: ${{ steps.react.outputs.reaction-id }} - steps: - name: Add eyes reaction to the triggering item id: react + if: github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment' || (github.event_name == 'pull_request') && (github.event.pull_request.head.repo.full_name == github.repository) uses: actions/github-script@v8 env: GITHUB_AW_REACTION: eyes diff --git a/pkg/workflow/compiler.go b/pkg/workflow/compiler.go index 097fbc58d18..16b53eee570 100644 --- a/pkg/workflow/compiler.go +++ b/pkg/workflow/compiler.go @@ -1747,17 +1747,6 @@ func (c *Compiler) buildJobs(data *WorkflowData, markdownPath string) error { activationJobCreated = true } - // Build add_reaction job only if ai-reaction is configured - if data.AIReaction != "" { - addReactionJob, err := c.buildAddReactionJob(data, activationJobCreated, frontmatter) - if err != nil { - return fmt.Errorf("failed to build add_reaction job: %w", err) - } - if err := c.jobManager.AddJob(addReactionJob); err != nil { - return fmt.Errorf("failed to add add_reaction job: %w", err) - } - } - // Build stop-time check job if stop-time is configured if data.StopTime != "" { stopTimeCheckJob, err := c.buildStopTimeCheckJob(data, activationJobCreated) @@ -2054,6 +2043,36 @@ func (c *Compiler) buildActivationJob(data *WorkflowData, checkMembershipJobCrea outputs["text"] = "${{ steps.compute-text.outputs.text }}" } + // Add reaction step if ai-reaction is configured + if data.AIReaction != "" { + reactionCondition := buildReactionCondition() + + steps = append(steps, fmt.Sprintf(" - name: Add %s reaction to the triggering item\n", data.AIReaction)) + steps = append(steps, " id: react\n") + steps = append(steps, fmt.Sprintf(" if: %s\n", reactionCondition.Render())) + steps = append(steps, " uses: actions/github-script@v8\n") + + // Add environment variables + steps = append(steps, " env:\n") + steps = append(steps, fmt.Sprintf(" GITHUB_AW_REACTION: %s\n", data.AIReaction)) + if data.Command != "" { + steps = append(steps, fmt.Sprintf(" GITHUB_AW_COMMAND: %s\n", data.Command)) + } + steps = append(steps, fmt.Sprintf(" GITHUB_AW_WORKFLOW_NAME: %q\n", data.Name)) + + steps = append(steps, " with:\n") + steps = append(steps, " script: |\n") + + // Add each line of the script with proper indentation + formattedScript := FormatJavaScriptForYAML(addReactionAndEditCommentScript) + steps = append(steps, formattedScript...) + + // Add reaction outputs + outputs["reaction_id"] = "${{ steps.react.outputs.reaction-id }}" + outputs["comment_id"] = "${{ steps.react.outputs.comment-id }}" + outputs["comment_url"] = "${{ steps.react.outputs.comment-url }}" + } + // If no steps have been added, add a dummy step to make the job valid // This can happen when the activation job is created only for an if condition if len(steps) == 0 { @@ -2083,8 +2102,11 @@ func (c *Compiler) buildActivationJob(data *WorkflowData, checkMembershipJobCrea activationCondition = data.If } - // No special permissions needed since role checks are handled by separate job + // Set permissions - add reaction permissions if reaction is configured var permissions string + if data.AIReaction != "" { + permissions = "permissions:\n discussions: write\n issues: write\n pull-requests: write" + } job := &Job{ Name: constants.ActivationJobName, diff --git a/pkg/workflow/compiler_test.go b/pkg/workflow/compiler_test.go index 57c7141271f..003626fa65a 100644 --- a/pkg/workflow/compiler_test.go +++ b/pkg/workflow/compiler_test.go @@ -2597,7 +2597,6 @@ Test workflow with reaction. // Check for reaction-specific content in generated YAML expectedStrings := []string{ - "add_reaction:", "GITHUB_AW_REACTION: eyes", "uses: actions/github-script@v8", } @@ -2608,10 +2607,21 @@ Test workflow with reaction. } } - // Verify four jobs are created (check_membership, activation, add_reaction, main) - check_membership and activation jobs are now created for permission checks + // Verify three jobs are created (check_membership, activation, main) - reaction step is now in activation job jobCount := strings.Count(yamlContent, "runs-on: ubuntu-latest") - if jobCount != 4 { - t.Errorf("Expected 4 jobs (check_membership, activation, add_reaction, main), found %d", jobCount) + if jobCount != 3 { + t.Errorf("Expected 3 jobs (check_membership, activation, main), found %d", jobCount) + } + + // Verify reaction step is in activation job, not a separate job + if strings.Contains(yamlContent, "add_reaction:") { + t.Error("Generated YAML should not contain separate add_reaction job") + } + + // Verify reaction step is in activation job + activationJobSection := extractJobSection(yamlContent, "activation") + if !strings.Contains(activationJobSection, "Add eyes reaction to the triggering item") { + t.Error("Activation job should contain the reaction step") } } @@ -2669,7 +2679,6 @@ Test workflow without explicit reaction (should not create reaction action). // Check that reaction-specific content is NOT in generated YAML unexpectedStrings := []string{ - "add_reaction:", "GITHUB_AW_REACTION:", "Add eyes reaction to the triggering item", } @@ -2680,7 +2689,7 @@ Test workflow without explicit reaction (should not create reaction action). } } - // Verify three jobs are created (check_membership, activation, main) - check_membership and activation jobs are now created for permission checks + // Verify three jobs are created (check_membership, activation, main) - no separate add_reaction job jobCount := strings.Count(yamlContent, "runs-on: ubuntu-latest") if jobCount != 3 { t.Errorf("Expected 3 jobs (check_membership, activation, main), found %d", jobCount) @@ -2742,7 +2751,6 @@ Test workflow with reaction and comment editing. // Check for enhanced reaction functionality in generated YAML expectedStrings := []string{ - "add_reaction:", "GITHUB_AW_REACTION: eyes", "uses: actions/github-script@v8", "addOrEditCommentWithWorkflowLink", // This should be in the new script @@ -2765,6 +2773,17 @@ Test workflow with reaction and comment editing. if !strings.Contains(yamlContent, "issue_comment") { t.Error("Generated YAML should reference issue_comment event handling") } + + // Verify reaction step is in activation job, not a separate job + if strings.Contains(yamlContent, "add_reaction:") { + t.Error("Generated YAML should not contain separate add_reaction job") + } + + // Verify reaction step is in activation job + activationJobSection := extractJobSection(yamlContent, "activation") + if !strings.Contains(activationJobSection, "Add eyes reaction to the triggering item") { + t.Error("Activation job should contain the reaction step") + } } // TestCommandReactionWithCommentEdit tests command workflows with reaction and comment editing @@ -2908,9 +2927,15 @@ Test command workflow that should automatically get "eyes" reaction. t.Error("Generated YAML should contain command environment variable") } - // Verify reaction job is created - if !strings.Contains(yamlContent, "add_reaction:") { - t.Error("Generated YAML should contain add_reaction job") + // Verify reaction step is in activation job, not a separate job + if strings.Contains(yamlContent, "add_reaction:") { + t.Error("Generated YAML should not contain separate add_reaction job") + } + + // Verify reaction step is in activation job + activationJobSection := extractJobSection(yamlContent, "activation") + if !strings.Contains(activationJobSection, "Add eyes reaction to the triggering item") { + t.Error("Activation job should contain the reaction step") } } @@ -5714,3 +5739,33 @@ tools: }) } } + +// extractJobSection extracts a specific job section from the YAML content +func extractJobSection(yamlContent, jobName string) string { + lines := strings.Split(yamlContent, "\n") + var jobLines []string + inJob := false + jobPrefix := " " + jobName + ":" + + for i, line := range lines { + if strings.HasPrefix(line, jobPrefix) { + inJob = true + jobLines = append(jobLines, line) + continue + } + + if inJob { + // If we hit another job at the same level (starts with " " and ends with ":"), stop + if strings.HasPrefix(line, " ") && strings.HasSuffix(line, ":") && !strings.HasPrefix(line, " ") { + break + } + // If we hit the end of jobs section, stop + if strings.HasPrefix(line, "jobs:") && i > 0 { + break + } + jobLines = append(jobLines, line) + } + } + + return strings.Join(jobLines, "\n") +} diff --git a/pkg/workflow/reaction.go b/pkg/workflow/reaction.go deleted file mode 100644 index dff94cfe2fc..00000000000 --- a/pkg/workflow/reaction.go +++ /dev/null @@ -1,59 +0,0 @@ -package workflow - -import ( - "fmt" - - "github.com/githubnext/gh-aw/pkg/constants" -) - -// buildAddReactionJob creates the add_reaction job -func (c *Compiler) buildAddReactionJob(data *WorkflowData, activationJobCreated bool, frontmatter map[string]any) (*Job, error) { - reactionCondition := buildReactionCondition() - - var steps []string - - steps = append(steps, fmt.Sprintf(" - name: Add %s reaction to the triggering item\n", data.AIReaction)) - steps = append(steps, " id: react\n") - steps = append(steps, " uses: actions/github-script@v8\n") - - // Add environment variables - steps = append(steps, " env:\n") - steps = append(steps, fmt.Sprintf(" GITHUB_AW_REACTION: %s\n", data.AIReaction)) - if data.Command != "" { - steps = append(steps, fmt.Sprintf(" GITHUB_AW_COMMAND: %s\n", data.Command)) - } - steps = append(steps, fmt.Sprintf(" GITHUB_AW_WORKFLOW_NAME: %q\n", data.Name)) - - steps = append(steps, " with:\n") - steps = append(steps, " script: |\n") - - // Add each line of the script with proper indentation - formattedScript := FormatJavaScriptForYAML(addReactionAndEditCommentScript) - steps = append(steps, formattedScript...) - - outputs := map[string]string{ - "reaction_id": "${{ steps.react.outputs.reaction-id }}", - "comment_id": "${{ steps.react.outputs.comment-id }}", - "comment_url": "${{ steps.react.outputs.comment-url }}", - } - - var depends []string - if activationJobCreated { - depends = []string{constants.ActivationJobName} // Depend on the activation job only if it exists - } - - // Set permissions - permissions := "permissions:\n discussions: write\n issues: write\n pull-requests: write" - - job := &Job{ - Name: "add_reaction", - If: reactionCondition.Render(), - RunsOn: "runs-on: ubuntu-latest", - Permissions: permissions, - Steps: steps, - Outputs: outputs, - Needs: depends, - } - - return job, nil -} diff --git a/pkg/workflow/reaction_outputs_test.go b/pkg/workflow/reaction_outputs_test.go index d6d8d459e3b..b72ff6c7ee5 100644 --- a/pkg/workflow/reaction_outputs_test.go +++ b/pkg/workflow/reaction_outputs_test.go @@ -70,7 +70,7 @@ This workflow should generate add_reaction job with comment outputs. } } - // Verify the outputs reference the react step + // Verify the outputs reference the react step - now in activation job if !strings.Contains(yamlContent, "steps.react.outputs.reaction-id") { t.Error("Generated YAML should contain reaction-id output reference") } @@ -80,6 +80,11 @@ This workflow should generate add_reaction job with comment outputs. if !strings.Contains(yamlContent, "steps.react.outputs.comment-url") { t.Error("Generated YAML should contain comment-url output reference") } + + // Verify reaction step is in activation job, not a separate add_reaction job + if strings.Contains(yamlContent, "add_reaction:") { + t.Error("Generated YAML should not contain separate add_reaction job") + } } // TestReactionJobWorkflowName tests that the add_reaction job includes GITHUB_AW_WORKFLOW_NAME environment variable @@ -128,9 +133,9 @@ This workflow should generate add_reaction job with GITHUB_AW_WORKFLOW_NAME envi t.Fatalf("Failed to generate YAML: %v", err) } - // Check that GITHUB_AW_WORKFLOW_NAME is set in the add_reaction job + // Check that GITHUB_AW_WORKFLOW_NAME is set if !strings.Contains(yamlContent, "GITHUB_AW_WORKFLOW_NAME:") { - t.Error("Generated YAML should contain GITHUB_AW_WORKFLOW_NAME environment variable in add_reaction job") + t.Error("Generated YAML should contain GITHUB_AW_WORKFLOW_NAME environment variable") } // Verify the workflow name is correctly set @@ -138,27 +143,32 @@ This workflow should generate add_reaction job with GITHUB_AW_WORKFLOW_NAME envi t.Error("Generated YAML should contain the correct workflow name value") } - // Ensure it's in the add_reaction job section - // Find the add_reaction job section - reactionJobStart := strings.Index(yamlContent, "add_reaction:") - if reactionJobStart == -1 { - t.Fatal("Could not find add_reaction job in generated YAML") + // Ensure it's in the activation job section (not a separate add_reaction job) + // Find the activation job section + activationJobStart := strings.Index(yamlContent, "activation:") + if activationJobStart == -1 { + t.Fatal("Could not find activation job in generated YAML") } // Find the next job or end of file nextJobStart := len(yamlContent) - lines := strings.Split(yamlContent[reactionJobStart:], "\n") + lines := strings.Split(yamlContent[activationJobStart:], "\n") for i, line := range lines[1:] { if strings.HasPrefix(line, " ") && strings.HasSuffix(line, ":") && !strings.HasPrefix(line, " ") { - nextJobStart = reactionJobStart + strings.Index(yamlContent[reactionJobStart:], lines[i+1]) + nextJobStart = activationJobStart + strings.Index(yamlContent[activationJobStart:], lines[i+1]) break } } - reactionJobSection := yamlContent[reactionJobStart:nextJobStart] + activationJobSection := yamlContent[activationJobStart:nextJobStart] - // Verify GITHUB_AW_WORKFLOW_NAME is in the add_reaction job section - if !strings.Contains(reactionJobSection, "GITHUB_AW_WORKFLOW_NAME:") { - t.Errorf("GITHUB_AW_WORKFLOW_NAME should be in the add_reaction job section\n%s", reactionJobSection) + // Verify GITHUB_AW_WORKFLOW_NAME is in the activation job section + if !strings.Contains(activationJobSection, "GITHUB_AW_WORKFLOW_NAME:") { + t.Errorf("GITHUB_AW_WORKFLOW_NAME should be in the activation job section\n%s", activationJobSection) + } + + // Verify no separate add_reaction job exists + if strings.Contains(yamlContent, "add_reaction:") { + t.Error("Generated YAML should not contain separate add_reaction job") } } diff --git a/pkg/workflow/task_and_reaction_permissions_test.go b/pkg/workflow/task_and_reaction_permissions_test.go index 22c0c0ffc7b..1db3570abd5 100644 --- a/pkg/workflow/task_and_reaction_permissions_test.go +++ b/pkg/workflow/task_and_reaction_permissions_test.go @@ -10,14 +10,14 @@ import ( ) func TestActivationAndAddReactionJobsPermissions(t *testing.T) { - // Test that activation and add_reaction jobs do not have contents permissions and checkout steps + // Test that activation job has correct permissions when reaction is configured tmpDir, err := os.MkdirTemp("", "permissions-test") if err != nil { t.Fatal(err) } defer os.RemoveAll(tmpDir) - // Create a test workflow with both activation job and add_reaction job + // Create a test workflow with reaction configured (reaction step is now in activation job) testContent := `--- on: issues: @@ -31,7 +31,7 @@ engine: claude # Test Workflow for Task and Add Reaction -This workflow should generate both activation and add_reaction jobs without contents permissions. +This workflow should generate activation job with reaction permissions. The activation job references text output: "${{ needs.activation.outputs.text }}" ` @@ -60,7 +60,7 @@ The activation job references text output: "${{ needs.activation.outputs.text }} lockContentStr := string(lockContent) - // Test 1: Verify activation job exists and has no contents permission + // Test 1: Verify activation job exists and has reaction permissions if !strings.Contains(lockContentStr, constants.ActivationJobName+":") { t.Error("Expected activation job to be present in generated workflow") } @@ -76,60 +76,24 @@ The activation job references text output: "${{ needs.activation.outputs.text }} t.Error("Activation job should not have contents permission") } - // Test 4: Verify add_reaction job exists and has no contents permission - if !strings.Contains(lockContentStr, "add_reaction:") { - t.Error("Expected add_reaction job to be present in generated workflow") + // Test 4: Verify no separate add_reaction job exists + if strings.Contains(lockContentStr, "add_reaction:") { + t.Error("Expected no separate add_reaction job - reaction should be in activation job") } - // Test 5: Verify add_reaction job has no checkout step - addReactionJobSection := extractJobSection(lockContentStr, "add_reaction") - if strings.Contains(addReactionJobSection, "actions/checkout") { - t.Error("Add_reaction job should not contain actions/checkout step") + // Test 5: Verify activation job has required permissions for reactions + if !strings.Contains(activationJobSection, "discussions: write") { + t.Error("Activation job should have discussions: write permission") } - - // Test 6: Verify add_reaction job has no contents permission - if strings.Contains(addReactionJobSection, "contents:") { - t.Error("Add_reaction job should not have contents permission") - } - - // Test 7: Verify add_reaction job still has required permissions - if !strings.Contains(addReactionJobSection, "discussions: write") { - t.Error("Add_reaction job should have discussions: write permission") + if !strings.Contains(activationJobSection, "issues: write") { + t.Error("Activation job should have issues: write permission") } - if !strings.Contains(addReactionJobSection, "issues: write") { - t.Error("Add_reaction job should still have issues: write permission") + if !strings.Contains(activationJobSection, "pull-requests: write") { + t.Error("Activation job should have pull-requests: write permission") } - if !strings.Contains(addReactionJobSection, "pull-requests: write") { - t.Error("Add_reaction job should still have pull-requests: write permission") + + // Test 6: Verify reaction step is in activation job + if !strings.Contains(activationJobSection, "Add eyes reaction to the triggering item") { + t.Error("Activation job should contain the reaction step") } } - -// extractJobSection extracts a specific job section from the YAML content -func extractJobSection(yamlContent, jobName string) string { - lines := strings.Split(yamlContent, "\n") - var jobLines []string - inJob := false - jobPrefix := " " + jobName + ":" - - for i, line := range lines { - if strings.HasPrefix(line, jobPrefix) { - inJob = true - jobLines = append(jobLines, line) - continue - } - - if inJob { - // If we hit another job at the same level (starts with " " and ends with ":"), stop - if strings.HasPrefix(line, " ") && strings.HasSuffix(line, ":") && !strings.HasPrefix(line, " ") { - break - } - // If we hit the end of jobs section, stop - if strings.HasPrefix(line, "jobs:") && i > 0 { - break - } - jobLines = append(jobLines, line) - } - } - - return strings.Join(jobLines, "\n") -} From 51744fa41589c0f8b53b51df4420016a6116eef3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Oct 2025 13:49:29 +0000 Subject: [PATCH 3/3] Fix code formatting (whitespace) Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/compiler.go | 2 +- pkg/workflow/compiler_test.go | 10 +++++----- pkg/workflow/reaction_outputs_test.go | 4 ++-- pkg/workflow/task_and_reaction_permissions_test.go | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pkg/workflow/compiler.go b/pkg/workflow/compiler.go index 16b53eee570..fef3a710a25 100644 --- a/pkg/workflow/compiler.go +++ b/pkg/workflow/compiler.go @@ -2046,7 +2046,7 @@ func (c *Compiler) buildActivationJob(data *WorkflowData, checkMembershipJobCrea // Add reaction step if ai-reaction is configured if data.AIReaction != "" { reactionCondition := buildReactionCondition() - + steps = append(steps, fmt.Sprintf(" - name: Add %s reaction to the triggering item\n", data.AIReaction)) steps = append(steps, " id: react\n") steps = append(steps, fmt.Sprintf(" if: %s\n", reactionCondition.Render())) diff --git a/pkg/workflow/compiler_test.go b/pkg/workflow/compiler_test.go index 003626fa65a..101fc341c6e 100644 --- a/pkg/workflow/compiler_test.go +++ b/pkg/workflow/compiler_test.go @@ -2612,12 +2612,12 @@ Test workflow with reaction. if jobCount != 3 { t.Errorf("Expected 3 jobs (check_membership, activation, main), found %d", jobCount) } - + // Verify reaction step is in activation job, not a separate job if strings.Contains(yamlContent, "add_reaction:") { t.Error("Generated YAML should not contain separate add_reaction job") } - + // Verify reaction step is in activation job activationJobSection := extractJobSection(yamlContent, "activation") if !strings.Contains(activationJobSection, "Add eyes reaction to the triggering item") { @@ -2773,12 +2773,12 @@ Test workflow with reaction and comment editing. if !strings.Contains(yamlContent, "issue_comment") { t.Error("Generated YAML should reference issue_comment event handling") } - + // Verify reaction step is in activation job, not a separate job if strings.Contains(yamlContent, "add_reaction:") { t.Error("Generated YAML should not contain separate add_reaction job") } - + // Verify reaction step is in activation job activationJobSection := extractJobSection(yamlContent, "activation") if !strings.Contains(activationJobSection, "Add eyes reaction to the triggering item") { @@ -2931,7 +2931,7 @@ Test command workflow that should automatically get "eyes" reaction. if strings.Contains(yamlContent, "add_reaction:") { t.Error("Generated YAML should not contain separate add_reaction job") } - + // Verify reaction step is in activation job activationJobSection := extractJobSection(yamlContent, "activation") if !strings.Contains(activationJobSection, "Add eyes reaction to the triggering item") { diff --git a/pkg/workflow/reaction_outputs_test.go b/pkg/workflow/reaction_outputs_test.go index b72ff6c7ee5..164747f4185 100644 --- a/pkg/workflow/reaction_outputs_test.go +++ b/pkg/workflow/reaction_outputs_test.go @@ -80,7 +80,7 @@ This workflow should generate add_reaction job with comment outputs. if !strings.Contains(yamlContent, "steps.react.outputs.comment-url") { t.Error("Generated YAML should contain comment-url output reference") } - + // Verify reaction step is in activation job, not a separate add_reaction job if strings.Contains(yamlContent, "add_reaction:") { t.Error("Generated YAML should not contain separate add_reaction job") @@ -166,7 +166,7 @@ This workflow should generate add_reaction job with GITHUB_AW_WORKFLOW_NAME envi if !strings.Contains(activationJobSection, "GITHUB_AW_WORKFLOW_NAME:") { t.Errorf("GITHUB_AW_WORKFLOW_NAME should be in the activation job section\n%s", activationJobSection) } - + // Verify no separate add_reaction job exists if strings.Contains(yamlContent, "add_reaction:") { t.Error("Generated YAML should not contain separate add_reaction job") diff --git a/pkg/workflow/task_and_reaction_permissions_test.go b/pkg/workflow/task_and_reaction_permissions_test.go index 1db3570abd5..bcb4123747c 100644 --- a/pkg/workflow/task_and_reaction_permissions_test.go +++ b/pkg/workflow/task_and_reaction_permissions_test.go @@ -91,7 +91,7 @@ The activation job references text output: "${{ needs.activation.outputs.text }} if !strings.Contains(activationJobSection, "pull-requests: write") { t.Error("Activation job should have pull-requests: write permission") } - + // Test 6: Verify reaction step is in activation job if !strings.Contains(activationJobSection, "Add eyes reaction to the triggering item") { t.Error("Activation job should contain the reaction step")