diff --git a/.github/workflows/agentic-campaign-generator.lock.yml b/.github/workflows/agentic-campaign-generator.lock.yml index 7dd43d6582..80a08bb09d 100644 --- a/.github/workflows/agentic-campaign-generator.lock.yml +++ b/.github/workflows/agentic-campaign-generator.lock.yml @@ -83,7 +83,7 @@ jobs: env: GH_AW_WORKFLOW_NAME: "Agentic Campaign Generator" GH_AW_LOCK_FOR_AGENT: "true" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"runStarted\":\"### :rocket: Campaign setup started\\nCreating a tracking Project and generating campaign files + orchestrator workflow.\\nNo action needed right now — the [{workflow_name}]({run_url}) will open a pull request and post the link + checklist back on this issue when ready.\\n\\u003e To stop this run: remove the label that started it. \\u003e Docs: https://githubnext.github.io/gh-aw/guides/campaigns/getting-started/\",\"runSuccess\":\"### :white_check_mark: Campaign setup complete\\nTracking Project created and pull request with generated campaign files is ready.\\nNext steps: - Review + merge the PR - Run the campaign from the Actions tab\\n\\u003e Docs: https://githubnext.github.io/gh-aw/guides/campaigns/getting-started/\",\"runFailure\":\"### :x: Campaign setup {status}\\nCommon causes: - `GH_AW_PROJECT_GITHUB_TOKEN` is missing or invalid - Token lacks access to GitHub Projects\\nAction required: - Fix the first error in the logs - Re-apply the label to re-run\\n\\u003e Troubleshooting: https://githubnext.github.io/gh-aw/guides/campaigns/flow/#when-something-goes-wrong \\u003e Docs: https://githubnext.github.io/gh-aw/guides/campaigns/getting-started/\"}" + GH_AW_SAFE_OUTPUT_MESSAGES: "{\"runStarted\":\"### :rocket: Campaign setup started\\nCreating a tracking Project and generating campaign files + orchestrator workflow.\\nNo action needed — the [{workflow_name}]({run_url}) will open a pull request and post the link + checklist back on this issue when ready.\\n\\u003e To stop this run: remove the label that started it.\\n\\u003e **Docs**: https://githubnext.github.io/gh-aw/guides/campaigns/getting-started/\",\"runSuccess\":\"### :white_check_mark: Campaign setup complete\\nTracking Project created and pull request with generated campaign files is ready.\\n**Next steps**: Review + merge the PR, then run the campaign from the Actions tab.\\n\\u003e **Docs**: https://githubnext.github.io/gh-aw/guides/campaigns/getting-started/\",\"runFailure\":\"### :x: Campaign setup {status}\\n**Common causes**:\\n- `GH_AW_PROJECT_GITHUB_TOKEN` is missing or invalid\\n- Token lacks access to GitHub Projects\\n**Action required**:\\n- Fix the first error in the logs\\n- Re-apply the label to re-run\\n\\u003e **Troubleshooting**: https://githubnext.github.io/gh-aw/guides/campaigns/flow/#when-something-goes-wrong\\n\\u003e **Docs**: https://githubnext.github.io/gh-aw/guides/campaigns/getting-started/\"}" with: script: | const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); @@ -1200,7 +1200,7 @@ jobs: GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.agent.outputs.secret_verification_result }} - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"runStarted\":\"### :rocket: Campaign setup started\\nCreating a tracking Project and generating campaign files + orchestrator workflow.\\nNo action needed right now — the [{workflow_name}]({run_url}) will open a pull request and post the link + checklist back on this issue when ready.\\n\\u003e To stop this run: remove the label that started it. \\u003e Docs: https://githubnext.github.io/gh-aw/guides/campaigns/getting-started/\",\"runSuccess\":\"### :white_check_mark: Campaign setup complete\\nTracking Project created and pull request with generated campaign files is ready.\\nNext steps: - Review + merge the PR - Run the campaign from the Actions tab\\n\\u003e Docs: https://githubnext.github.io/gh-aw/guides/campaigns/getting-started/\",\"runFailure\":\"### :x: Campaign setup {status}\\nCommon causes: - `GH_AW_PROJECT_GITHUB_TOKEN` is missing or invalid - Token lacks access to GitHub Projects\\nAction required: - Fix the first error in the logs - Re-apply the label to re-run\\n\\u003e Troubleshooting: https://githubnext.github.io/gh-aw/guides/campaigns/flow/#when-something-goes-wrong \\u003e Docs: https://githubnext.github.io/gh-aw/guides/campaigns/getting-started/\"}" + GH_AW_SAFE_OUTPUT_MESSAGES: "{\"runStarted\":\"### :rocket: Campaign setup started\\nCreating a tracking Project and generating campaign files + orchestrator workflow.\\nNo action needed — the [{workflow_name}]({run_url}) will open a pull request and post the link + checklist back on this issue when ready.\\n\\u003e To stop this run: remove the label that started it.\\n\\u003e **Docs**: https://githubnext.github.io/gh-aw/guides/campaigns/getting-started/\",\"runSuccess\":\"### :white_check_mark: Campaign setup complete\\nTracking Project created and pull request with generated campaign files is ready.\\n**Next steps**: Review + merge the PR, then run the campaign from the Actions tab.\\n\\u003e **Docs**: https://githubnext.github.io/gh-aw/guides/campaigns/getting-started/\",\"runFailure\":\"### :x: Campaign setup {status}\\n**Common causes**:\\n- `GH_AW_PROJECT_GITHUB_TOKEN` is missing or invalid\\n- Token lacks access to GitHub Projects\\n**Action required**:\\n- Fix the first error in the logs\\n- Re-apply the label to re-run\\n\\u003e **Troubleshooting**: https://githubnext.github.io/gh-aw/guides/campaigns/flow/#when-something-goes-wrong\\n\\u003e **Docs**: https://githubnext.github.io/gh-aw/guides/campaigns/getting-started/\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1219,7 +1219,7 @@ jobs: GH_AW_WORKFLOW_NAME: "Agentic Campaign Generator" GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.result }} - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"runStarted\":\"### :rocket: Campaign setup started\\nCreating a tracking Project and generating campaign files + orchestrator workflow.\\nNo action needed right now — the [{workflow_name}]({run_url}) will open a pull request and post the link + checklist back on this issue when ready.\\n\\u003e To stop this run: remove the label that started it. \\u003e Docs: https://githubnext.github.io/gh-aw/guides/campaigns/getting-started/\",\"runSuccess\":\"### :white_check_mark: Campaign setup complete\\nTracking Project created and pull request with generated campaign files is ready.\\nNext steps: - Review + merge the PR - Run the campaign from the Actions tab\\n\\u003e Docs: https://githubnext.github.io/gh-aw/guides/campaigns/getting-started/\",\"runFailure\":\"### :x: Campaign setup {status}\\nCommon causes: - `GH_AW_PROJECT_GITHUB_TOKEN` is missing or invalid - Token lacks access to GitHub Projects\\nAction required: - Fix the first error in the logs - Re-apply the label to re-run\\n\\u003e Troubleshooting: https://githubnext.github.io/gh-aw/guides/campaigns/flow/#when-something-goes-wrong \\u003e Docs: https://githubnext.github.io/gh-aw/guides/campaigns/getting-started/\"}" + GH_AW_SAFE_OUTPUT_MESSAGES: "{\"runStarted\":\"### :rocket: Campaign setup started\\nCreating a tracking Project and generating campaign files + orchestrator workflow.\\nNo action needed — the [{workflow_name}]({run_url}) will open a pull request and post the link + checklist back on this issue when ready.\\n\\u003e To stop this run: remove the label that started it.\\n\\u003e **Docs**: https://githubnext.github.io/gh-aw/guides/campaigns/getting-started/\",\"runSuccess\":\"### :white_check_mark: Campaign setup complete\\nTracking Project created and pull request with generated campaign files is ready.\\n**Next steps**: Review + merge the PR, then run the campaign from the Actions tab.\\n\\u003e **Docs**: https://githubnext.github.io/gh-aw/guides/campaigns/getting-started/\",\"runFailure\":\"### :x: Campaign setup {status}\\n**Common causes**:\\n- `GH_AW_PROJECT_GITHUB_TOKEN` is missing or invalid\\n- Token lacks access to GitHub Projects\\n**Action required**:\\n- Fix the first error in the logs\\n- Re-apply the label to re-run\\n\\u003e **Troubleshooting**: https://githubnext.github.io/gh-aw/guides/campaigns/flow/#when-something-goes-wrong\\n\\u003e **Docs**: https://githubnext.github.io/gh-aw/guides/campaigns/getting-started/\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1460,7 +1460,7 @@ jobs: timeout-minutes: 15 env: GH_AW_ENGINE_ID: "claude" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"runStarted\":\"### :rocket: Campaign setup started\\nCreating a tracking Project and generating campaign files + orchestrator workflow.\\nNo action needed right now — the [{workflow_name}]({run_url}) will open a pull request and post the link + checklist back on this issue when ready.\\n\\u003e To stop this run: remove the label that started it. \\u003e Docs: https://githubnext.github.io/gh-aw/guides/campaigns/getting-started/\",\"runSuccess\":\"### :white_check_mark: Campaign setup complete\\nTracking Project created and pull request with generated campaign files is ready.\\nNext steps: - Review + merge the PR - Run the campaign from the Actions tab\\n\\u003e Docs: https://githubnext.github.io/gh-aw/guides/campaigns/getting-started/\",\"runFailure\":\"### :x: Campaign setup {status}\\nCommon causes: - `GH_AW_PROJECT_GITHUB_TOKEN` is missing or invalid - Token lacks access to GitHub Projects\\nAction required: - Fix the first error in the logs - Re-apply the label to re-run\\n\\u003e Troubleshooting: https://githubnext.github.io/gh-aw/guides/campaigns/flow/#when-something-goes-wrong \\u003e Docs: https://githubnext.github.io/gh-aw/guides/campaigns/getting-started/\"}" + GH_AW_SAFE_OUTPUT_MESSAGES: "{\"runStarted\":\"### :rocket: Campaign setup started\\nCreating a tracking Project and generating campaign files + orchestrator workflow.\\nNo action needed — the [{workflow_name}]({run_url}) will open a pull request and post the link + checklist back on this issue when ready.\\n\\u003e To stop this run: remove the label that started it.\\n\\u003e **Docs**: https://githubnext.github.io/gh-aw/guides/campaigns/getting-started/\",\"runSuccess\":\"### :white_check_mark: Campaign setup complete\\nTracking Project created and pull request with generated campaign files is ready.\\n**Next steps**: Review + merge the PR, then run the campaign from the Actions tab.\\n\\u003e **Docs**: https://githubnext.github.io/gh-aw/guides/campaigns/getting-started/\",\"runFailure\":\"### :x: Campaign setup {status}\\n**Common causes**:\\n- `GH_AW_PROJECT_GITHUB_TOKEN` is missing or invalid\\n- Token lacks access to GitHub Projects\\n**Action required**:\\n- Fix the first error in the logs\\n- Re-apply the label to re-run\\n\\u003e **Troubleshooting**: https://githubnext.github.io/gh-aw/guides/campaigns/flow/#when-something-goes-wrong\\n\\u003e **Docs**: https://githubnext.github.io/gh-aw/guides/campaigns/getting-started/\"}" GH_AW_WORKFLOW_ID: "agentic-campaign-generator" GH_AW_WORKFLOW_NAME: "Agentic Campaign Generator" outputs: diff --git a/.github/workflows/agentic-campaign-generator.md b/.github/workflows/agentic-campaign-generator.md index 4bde3af975..994e566568 100644 --- a/.github/workflows/agentic-campaign-generator.md +++ b/.github/workflows/agentic-campaign-generator.md @@ -72,31 +72,35 @@ safe-outputs: Creating a tracking Project and generating campaign files + orchestrator workflow. -No action needed right now — the [{workflow_name}]({run_url}) will open a pull request and post the link + checklist back on this issue when ready. +No action needed — the [{workflow_name}]({run_url}) will open a pull request and post the link + checklist back on this issue when ready. > To stop this run: remove the label that started it. -> Docs: https://githubnext.github.io/gh-aw/guides/campaigns/getting-started/" + +> **Docs**: https://githubnext.github.io/gh-aw/guides/campaigns/getting-started/" run-success: "### :white_check_mark: Campaign setup complete Tracking Project created and pull request with generated campaign files is ready. -Next steps: -- Review + merge the PR -- Run the campaign from the Actions tab +**Next steps**: Review + merge the PR, then run the campaign from the Actions tab. -> Docs: https://githubnext.github.io/gh-aw/guides/campaigns/getting-started/" +> **Docs**: https://githubnext.github.io/gh-aw/guides/campaigns/getting-started/" run-failure: "### :x: Campaign setup {status} -Common causes: +**Common causes**: + - `GH_AW_PROJECT_GITHUB_TOKEN` is missing or invalid + - Token lacks access to GitHub Projects -Action required: +**Action required**: + - Fix the first error in the logs + - Re-apply the label to re-run -> Troubleshooting: https://githubnext.github.io/gh-aw/guides/campaigns/flow/#when-something-goes-wrong -> Docs: https://githubnext.github.io/gh-aw/guides/campaigns/getting-started/" +> **Troubleshooting**: https://githubnext.github.io/gh-aw/guides/campaigns/flow/#when-something-goes-wrong + +> **Docs**: https://githubnext.github.io/gh-aw/guides/campaigns/getting-started/" timeout-minutes: 10 --- diff --git a/actions/setup/js/update_activation_comment.cjs b/actions/setup/js/update_activation_comment.cjs index c73694e6a2..c3525948bb 100644 --- a/actions/setup/js/update_activation_comment.cjs +++ b/actions/setup/js/update_activation_comment.cjs @@ -2,6 +2,7 @@ /// const { getErrorMessage } = require("./error_helpers.cjs"); +const { getMessages } = require("./messages_core.cjs"); /** * Update the activation comment with a link to the created pull request or issue @@ -44,13 +45,9 @@ async function updateActivationCommentWithMessage(github, context, core, message const commentId = process.env.GH_AW_COMMENT_ID; const commentRepo = process.env.GH_AW_COMMENT_REPO; - // If no comment was created in activation, skip updating - if (!commentId) { - core.info("No activation comment to update (GH_AW_COMMENT_ID not set)"); - return; - } - - core.info(`Updating activation comment ${commentId}`); + // Check if append-only-comments is enabled + const messagesConfig = getMessages(); + const appendOnlyComments = messagesConfig?.appendOnlyComments === true; // Parse comment repo (format: "owner/repo") with validation let repoOwner = context.repo.owner; @@ -65,6 +62,98 @@ async function updateActivationCommentWithMessage(github, context, core, message } } + // Append-only mode: create a new comment instead of updating the activation comment + if (appendOnlyComments) { + core.info("Append-only-comments enabled: creating a new comment"); + try { + const eventName = context.eventName; + + // Discussions: create a new discussion comment (threaded reply for discussion_comment) + if (eventName === "discussion" || eventName === "discussion_comment") { + const discussionNumber = context.payload?.discussion?.number; + if (!discussionNumber) { + core.warning("Unable to determine discussion number for append-only comment; skipping"); + return; + } + + const { repository } = await github.graphql( + ` + query($owner: String!, $repo: String!, $num: Int!) { + repository(owner: $owner, name: $repo) { + discussion(number: $num) { + id + } + } + }`, + { owner: repoOwner, repo: repoName, num: discussionNumber } + ); + + const discussionId = repository?.discussion?.id; + if (!discussionId) { + core.warning("Unable to resolve discussion id for append-only comment; skipping"); + return; + } + + const replyToId = eventName === "discussion_comment" ? context.payload?.comment?.node_id : null; + const mutation = replyToId + ? `mutation($dId: ID!, $body: String!, $replyToId: ID!) { + addDiscussionComment(input: { discussionId: $dId, body: $body, replyToId: $replyToId }) { + comment { id url } + } + }` + : `mutation($dId: ID!, $body: String!) { + addDiscussionComment(input: { discussionId: $dId, body: $body }) { + comment { id url } + } + }`; + + const variables = replyToId ? { dId: discussionId, body: message, replyToId } : { dId: discussionId, body: message }; + const result = await github.graphql(mutation, variables); + const created = result?.addDiscussionComment?.comment; + const successMessage = label ? `Successfully created append-only discussion comment with ${label} link` : "Successfully created append-only discussion comment"; + core.info(successMessage); + if (created?.id) core.info(`Comment ID: ${created.id}`); + if (created?.url) core.info(`Comment URL: ${created.url}`); + return; + } + + // Issues/PRs: determine issue number from event payload and create a new issue comment + const issueNumber = context.payload?.issue?.number || context.payload?.pull_request?.number; + if (!issueNumber) { + core.warning("Unable to determine issue/PR number for append-only comment; skipping"); + return; + } + + const response = await github.request("POST /repos/{owner}/{repo}/issues/{issue_number}/comments", { + owner: repoOwner, + repo: repoName, + issue_number: issueNumber, + body: message, + headers: { + Accept: "application/vnd.github+json", + }, + }); + + const successMessage = label ? `Successfully created append-only comment with ${label} link` : "Successfully created append-only comment"; + core.info(successMessage); + if (response?.data?.id) core.info(`Comment ID: ${response.data.id}`); + if (response?.data?.html_url) core.info(`Comment URL: ${response.data.html_url}`); + return; + } catch (error) { + // Don't fail the workflow if we can't create the comment - just log a warning + core.warning(`Failed to create append-only comment: ${getErrorMessage(error)}`); + return; + } + } + + // Standard mode: update the existing activation comment + // If no comment was created in activation, skip updating + if (!commentId) { + core.info("No activation comment to update (GH_AW_COMMENT_ID not set)"); + return; + } + + core.info(`Updating activation comment ${commentId}`); core.info(`Updating comment in ${repoOwner}/${repoName}`); // Check if this is a discussion comment (GraphQL node ID format) diff --git a/actions/setup/js/update_activation_comment.test.cjs b/actions/setup/js/update_activation_comment.test.cjs index 5eda58e8e3..38145b2168 100644 --- a/actions/setup/js/update_activation_comment.test.cjs +++ b/actions/setup/js/update_activation_comment.test.cjs @@ -5,11 +5,25 @@ const createTestableFunction = scriptContent => { const beforeMainCall = scriptContent.match(/^([\s\S]*?)\s*module\.exports\s*=\s*{[\s\S]*?};?\s*$/); if (!beforeMainCall) throw new Error("Could not extract script content before module.exports"); let scriptBody = beforeMainCall[1]; - // Mock the error_helpers module + // Mock the error_helpers and messages_core modules const mockRequire = module => { if (module === "./error_helpers.cjs") { return { getErrorMessage: error => (error instanceof Error ? error.message : String(error)) }; } + if (module === "./messages_core.cjs") { + return { + getMessages: () => { + // Return messages config from environment + const messagesEnv = process.env.GH_AW_SAFE_OUTPUT_MESSAGES; + if (!messagesEnv) return null; + try { + return JSON.parse(messagesEnv); + } catch (e) { + return null; + } + }, + }; + } throw new Error(`Module ${module} not mocked in test`); }; return new Function(`\n const { github, core, context, process } = arguments[0];\n const require = ${mockRequire.toString()};\n \n ${scriptBody}\n \n return { updateActivationComment };\n `); @@ -157,5 +171,142 @@ describe("update_activation_comment.cjs", () => { expect.objectContaining({ commentId: "DC_kwDOABCDEF4ABCDEF", body: expect.stringContaining("✅ Issue created: [#99](https://github.com/testowner/testrepo/issues/99)") }) ), expect(mockDependencies.core.info).toHaveBeenCalledWith("Successfully updated discussion comment with issue link")); + }), + describe("append-only-comments mode", () => { + it("should create new issue comment instead of updating when append-only-comments is enabled", async () => { + mockDependencies.process.env.GH_AW_COMMENT_ID = "123456"; + mockDependencies.process.env.GH_AW_COMMENT_REPO = "testowner/testrepo"; + mockDependencies.process.env.GH_AW_SAFE_OUTPUT_MESSAGES = JSON.stringify({ + appendOnlyComments: true, + }); + mockDependencies.context.eventName = "issues"; + mockDependencies.context.payload = { issue: { number: 42 } }; + + mockDependencies.github.request.mockImplementation(async (method, params) => { + if (method.startsWith("POST")) { + return { + data: { + id: 789012, + html_url: "https://github.com/testowner/testrepo/issues/42#issuecomment-789012", + }, + }; + } + return { data: {} }; + }); + + const { updateActivationComment } = createFunctionFromScript(mockDependencies); + await updateActivationComment(mockDependencies.github, mockDependencies.context, mockDependencies.core, "https://github.com/testowner/testrepo/pull/99", 99); + + // Should create a new comment, not update the existing one + expect(mockDependencies.github.request).toHaveBeenCalledWith( + "POST /repos/{owner}/{repo}/issues/{issue_number}/comments", + expect.objectContaining({ + owner: "testowner", + repo: "testrepo", + issue_number: 42, + body: expect.stringContaining("✅ Pull request created: [#99]"), + }) + ); + expect(mockDependencies.core.info).toHaveBeenCalledWith("Append-only-comments enabled: creating a new comment"); + expect(mockDependencies.core.info).toHaveBeenCalledWith("Successfully created append-only comment with pull request link"); + + // Should NOT call GET or PATCH for updating existing comment + expect(mockDependencies.github.request).not.toHaveBeenCalledWith("GET /repos/{owner}/{repo}/issues/comments/{comment_id}", expect.any(Object)); + expect(mockDependencies.github.request).not.toHaveBeenCalledWith("PATCH /repos/{owner}/{repo}/issues/comments/{comment_id}", expect.any(Object)); + }); + + it("should create new discussion comment instead of updating when append-only-comments is enabled", async () => { + mockDependencies.process.env.GH_AW_COMMENT_ID = "DC_kwDOABCDEF4ABCDEF"; + mockDependencies.process.env.GH_AW_COMMENT_REPO = "testowner/testrepo"; + mockDependencies.process.env.GH_AW_SAFE_OUTPUT_MESSAGES = JSON.stringify({ + appendOnlyComments: true, + }); + mockDependencies.context.eventName = "discussion"; + mockDependencies.context.payload = { discussion: { number: 10 } }; + + mockDependencies.github.graphql.mockImplementation(async (query, params) => { + if (query.includes("query") && query.includes("discussion(number")) { + return { + repository: { + discussion: { + id: "D_kwDOABCDEF4ABCDEF", + }, + }, + }; + } + if (query.includes("mutation") && query.includes("addDiscussionComment")) { + return { + addDiscussionComment: { + comment: { + id: "DC_kwDOABCDEF4NEW", + url: "https://github.com/testowner/testrepo/discussions/10#discussioncomment-new", + }, + }, + }; + } + return {}; + }); + + const { updateActivationComment } = createFunctionFromScript(mockDependencies); + await updateActivationComment(mockDependencies.github, mockDependencies.context, mockDependencies.core, "https://github.com/testowner/testrepo/pull/99", 99); + + // Should create a new discussion comment + expect(mockDependencies.github.graphql).toHaveBeenCalledWith( + expect.stringContaining("mutation"), + expect.objectContaining({ + dId: "D_kwDOABCDEF4ABCDEF", + body: expect.stringContaining("✅ Pull request created: [#99]"), + }) + ); + expect(mockDependencies.core.info).toHaveBeenCalledWith("Successfully created append-only discussion comment with pull request link"); + + // Should NOT call updateDiscussionComment mutation + expect(mockDependencies.github.graphql).not.toHaveBeenCalledWith(expect.stringContaining("updateDiscussionComment"), expect.any(Object)); + }); + + it("should handle missing issue number gracefully in append-only mode", async () => { + mockDependencies.process.env.GH_AW_COMMENT_ID = "123456"; + mockDependencies.process.env.GH_AW_SAFE_OUTPUT_MESSAGES = JSON.stringify({ + appendOnlyComments: true, + }); + mockDependencies.context.eventName = "issues"; + mockDependencies.context.payload = {}; // No issue number + + const { updateActivationComment } = createFunctionFromScript(mockDependencies); + await updateActivationComment(mockDependencies.github, mockDependencies.context, mockDependencies.core, "https://github.com/testowner/testrepo/pull/99", 99); + + expect(mockDependencies.core.warning).toHaveBeenCalledWith("Unable to determine issue/PR number for append-only comment; skipping"); + expect(mockDependencies.github.request).not.toHaveBeenCalled(); + }); + + it("should update existing comment when append-only-comments is false", async () => { + mockDependencies.process.env.GH_AW_COMMENT_ID = "123456"; + mockDependencies.process.env.GH_AW_COMMENT_REPO = "testowner/testrepo"; + mockDependencies.process.env.GH_AW_SAFE_OUTPUT_MESSAGES = JSON.stringify({ + appendOnlyComments: false, + }); + + mockDependencies.github.request.mockImplementation(async (method, params) => { + if (method.startsWith("GET")) { + return { data: { body: "Original comment" } }; + } + if (method.startsWith("PATCH")) { + return { + data: { + id: 123456, + html_url: "https://github.com/testowner/testrepo/issues/1#issuecomment-123456", + }, + }; + } + return { data: {} }; + }); + + const { updateActivationComment } = createFunctionFromScript(mockDependencies); + await updateActivationComment(mockDependencies.github, mockDependencies.context, mockDependencies.core, "https://github.com/testowner/testrepo/pull/99", 99); + + // Should update the existing comment (standard behavior) + expect(mockDependencies.github.request).toHaveBeenCalledWith("GET /repos/{owner}/{repo}/issues/comments/{comment_id}", expect.objectContaining({ comment_id: 123456 })); + expect(mockDependencies.github.request).toHaveBeenCalledWith("PATCH /repos/{owner}/{repo}/issues/comments/{comment_id}", expect.objectContaining({ comment_id: 123456 })); + }); })); });