diff --git a/.github/workflows/copilot-review-tests.lock.yml b/.github/workflows/copilot-review-tests.lock.yml index a4d08874d6c5..ab7bdeff1ff8 100644 --- a/.github/workflows/copilot-review-tests.lock.yml +++ b/.github/workflows/copilot-review-tests.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"b5b5f8dd166eb86805c9e5a24dcac9b37606ae820569801fbc9eb323073f8d5e","body_hash":"44becb5921a41041d2bfe4f01ab40a77a42cabe36e4b2f68d213eba783e7bde3","compiler_version":"v0.79.8","strict":true,"agent_id":"copilot","agent_model":"claude-sonnet-4.6","engine_versions":{"copilot":"1.0.60"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"478424151672a1d00be2f61c9d74e9a372766a90126cf5b02fa0c03d0e1a1802","body_hash":"44becb5921a41041d2bfe4f01ab40a77a42cabe36e4b2f68d213eba783e7bde3","compiler_version":"v0.79.8","strict":true,"agent_id":"copilot","agent_model":"claude-sonnet-4.6","engine_versions":{"copilot":"1.0.60"}} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"df4cb1c069e1874edd31b4311f1884172cec0e10","version":"v6.0.3"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"c0338fef4749d08c21f8f975fb0e37efa17dda47","version":"v0.79.8"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2","digest":"sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2","digest":"sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2","digest":"sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.25","digest":"sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa"},{"image":"ghcr.io/github/github-mcp-server:v1.1.2","digest":"sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c","pinned_image":"ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c"}]} # This file was automatically generated by gh-aw (v0.79.8). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -53,6 +53,8 @@ on: types: - created - edited + # permissions: # Permissions applied to pre-activation job + # issues: write # roles: # Roles processed as role check in pre-activation job # - admin # Roles processed as role check in pre-activation job # - maintain # Roles processed as role check in pre-activation job @@ -89,6 +91,12 @@ on: # else # echo "should_run=false" >> "$GITHUB_OUTPUT" # fi + # - if: github.event_name == 'issue_comment' && steps.exact_command.outputs.should_run == 'true' + # name: Hide the /review tests command comment as resolved when authorized + # uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 + # with: + # github-token: ${{ github.token }} + # script: "// Only hide when the command is exactly `/review tests` (should_run) AND the\n// commenter is an authorized collaborator (write/maintain/admin). This mirrors\n// the workflow's own role gate but is self-contained, so an unauthorized user's\n// comment is always left visible. A failed hide must not block activation.\n// Only act on newly-created comments. The gh-aw slash_command trigger also fires\n// on `edited`, so without this guard, editing any existing comment to say\n// `/review tests` would minimize that comment (and collapse its entire history).\nif (context.payload.action !== 'created') {\n core.info('Skipping hide: comment was edited, not created.');\n return;\n}\nconst { owner, repo } = context.repo;\nconst actor = context.actor;\nlet permission = 'none';\ntry {\n const res = await github.rest.repos.getCollaboratorPermissionLevel({ owner, repo, username: actor });\n permission = res.data.permission;\n} catch (e) {\n core.info(`Permission lookup for ${actor} failed: ${e.message}`);\n}\n// Must mirror the workflow `roles:` frontmatter (admin/maintain/write) — keep in sync.\nif (!['admin', 'maintain', 'write'].includes(permission)) {\n core.info(`Actor ${actor} is not an authorized collaborator (${permission}); leaving the /review tests comment.`);\n return;\n}\n// Minimize (hide as resolved) rather than delete: the rerun scanner replays the PR's\n// REST comment history, and minimized comments are still returned by the REST list\n// endpoint — only collapsed in the web UI. node_id is the comment's GraphQL global id.\nconst subjectId = context.payload.comment.node_id;\ntry {\n await github.graphql(\n `mutation($id: ID!) {\n minimizeComment(input: { subjectId: $id, classifier: RESOLVED }) {\n minimizedComment { isMinimized }\n }\n }`,\n { id: subjectId }\n );\n core.info(`Hid /review tests command comment ${subjectId} as resolved.`);\n} catch (e) {\n core.warning(`Could not hide /review tests command comment ${subjectId}: ${e.message}`);\n}\n" workflow_dispatch: inputs: aw_context: @@ -1528,6 +1536,8 @@ jobs: (!(github.event_name == 'pull_request_review_comment') || !contains(fromJSON('["CONTRIBUTOR","FIRST_TIME_CONTRIBUTOR","FIRST_TIMER","MANNEQUIN","NONE"]'), github.event.comment.author_association)) runs-on: ubuntu-slim environment: gh-aw-agents + permissions: + issues: write outputs: activated: ${{ steps.check_membership.outputs.is_team_member == 'true' && steps.check_command_position.outputs.command_position_ok == 'true' }} exact_command_result: ${{ steps.exact_command.outcome }} @@ -1590,6 +1600,54 @@ jobs: COMMENT_BODY: ${{ github.event.comment.body }} EVENT_NAME: ${{ github.event_name }} ISSUE_PULL_REQUEST_URL: ${{ github.event.issue.pull_request.url }} + - name: Hide the /review tests command comment as resolved when authorized + if: github.event_name == 'issue_comment' && steps.exact_command.outputs.should_run == 'true' + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ github.token }} + script: | + // Only hide when the command is exactly `/review tests` (should_run) AND the + // commenter is an authorized collaborator (write/maintain/admin). This mirrors + // the workflow's own role gate but is self-contained, so an unauthorized user's + // comment is always left visible. A failed hide must not block activation. + // Only act on newly-created comments. The gh-aw slash_command trigger also fires + // on `edited`, so without this guard, editing any existing comment to say + // `/review tests` would minimize that comment (and collapse its entire history). + if (context.payload.action !== 'created') { + core.info('Skipping hide: comment was edited, not created.'); + return; + } + const { owner, repo } = context.repo; + const actor = context.actor; + let permission = 'none'; + try { + const res = await github.rest.repos.getCollaboratorPermissionLevel({ owner, repo, username: actor }); + permission = res.data.permission; + } catch (e) { + core.info(`Permission lookup for ${actor} failed: ${e.message}`); + } + // Must mirror the workflow `roles:` frontmatter (admin/maintain/write) — keep in sync. + if (!['admin', 'maintain', 'write'].includes(permission)) { + core.info(`Actor ${actor} is not an authorized collaborator (${permission}); leaving the /review tests comment.`); + return; + } + // Minimize (hide as resolved) rather than delete: the rerun scanner replays the PR's + // REST comment history, and minimized comments are still returned by the REST list + // endpoint — only collapsed in the web UI. node_id is the comment's GraphQL global id. + const subjectId = context.payload.comment.node_id; + try { + await github.graphql( + `mutation($id: ID!) { + minimizeComment(input: { subjectId: $id, classifier: RESOLVED }) { + minimizedComment { isMinimized } + } + }`, + { id: subjectId } + ); + core.info(`Hid /review tests command comment ${subjectId} as resolved.`); + } catch (e) { + core.warning(`Could not hide /review tests command comment ${subjectId}: ${e.message}`); + } safe_outputs: needs: diff --git a/.github/workflows/copilot-review-tests.md b/.github/workflows/copilot-review-tests.md index 54c0f5b48348..fc9f8ef27288 100644 --- a/.github/workflows/copilot-review-tests.md +++ b/.github/workflows/copilot-review-tests.md @@ -12,6 +12,11 @@ on: pull_request_review_comment: [contributor, first_time_contributor, first_timer, mannequin, none] reaction: none status-comment: false + # Grant the pre-activation job (the on.steps below) issues:write so it can hide (minimize + # as resolved) the triggering `/review tests` comment once the command is recognized and + # authorized. Minimizing requires the same issues:write scope that deletion did. + permissions: + issues: write steps: - name: Confirm exact /review tests command id: exact_command @@ -31,6 +36,54 @@ on: else echo "should_run=false" >> "$GITHUB_OUTPUT" fi + - name: Hide the /review tests command comment as resolved when authorized + if: github.event_name == 'issue_comment' && steps.exact_command.outputs.should_run == 'true' + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ github.token }} + script: | + // Only hide when the command is exactly `/review tests` (should_run) AND the + // commenter is an authorized collaborator (write/maintain/admin). This mirrors + // the workflow's own role gate but is self-contained, so an unauthorized user's + // comment is always left visible. A failed hide must not block activation. + // Only act on newly-created comments. The gh-aw slash_command trigger also fires + // on `edited`, so without this guard, editing any existing comment to say + // `/review tests` would minimize that comment (and collapse its entire history). + if (context.payload.action !== 'created') { + core.info('Skipping hide: comment was edited, not created.'); + return; + } + const { owner, repo } = context.repo; + const actor = context.actor; + let permission = 'none'; + try { + const res = await github.rest.repos.getCollaboratorPermissionLevel({ owner, repo, username: actor }); + permission = res.data.permission; + } catch (e) { + core.info(`Permission lookup for ${actor} failed: ${e.message}`); + } + // Must mirror the workflow `roles:` frontmatter (admin/maintain/write) — keep in sync. + if (!['admin', 'maintain', 'write'].includes(permission)) { + core.info(`Actor ${actor} is not an authorized collaborator (${permission}); leaving the /review tests comment.`); + return; + } + // Minimize (hide as resolved) rather than delete: the rerun scanner replays the PR's + // REST comment history, and minimized comments are still returned by the REST list + // endpoint — only collapsed in the web UI. node_id is the comment's GraphQL global id. + const subjectId = context.payload.comment.node_id; + try { + await github.graphql( + `mutation($id: ID!) { + minimizeComment(input: { subjectId: $id, classifier: RESOLVED }) { + minimizedComment { isMinimized } + } + }`, + { id: subjectId } + ); + core.info(`Hid /review tests command comment ${subjectId} as resolved.`); + } catch (e) { + core.warning(`Could not hide /review tests command comment ${subjectId}: ${e.message}`); + } workflow_dispatch: inputs: pr_number: diff --git a/.github/workflows/review-trigger.yml b/.github/workflows/review-trigger.yml index 2c5641f84b33..7ebf3d708251 100644 --- a/.github/workflows/review-trigger.yml +++ b/.github/workflows/review-trigger.yml @@ -139,6 +139,28 @@ jobs: "Reason: ${{ steps.rerun.outputs.reason }}" >> $env:GITHUB_STEP_SUMMARY "Label: ${{ steps.rerun.outputs.label }}" >> $env:GITHUB_STEP_SUMMARY + - name: Hide the /review rerun command comment as resolved + if: github.event_name == 'issue_comment' && steps.rerun.outputs.eligible == 'true' + env: + GH_TOKEN: ${{ github.token }} + COMMENT_NODE_ID: ${{ github.event.comment.node_id }} + run: | + # Only collapse once a rerun was actually triggered (eligible == 'true'). Ineligible + # reruns (no-ai-summary / review-in-progress / no-new-activity) keep the comment fully + # visible as the implicit "seen, nothing changed" signal. + # + # We MINIMIZE (hide as resolved) rather than delete: the rerun scanner replays the + # PR's REST comment history to reconstruct rerun state (Resolve-RerunEligibility.ps1 / + # Query-RerunReadyPRs.ps1 / Get-LatestRerunCommentBefore), and minimized comments are + # still returned by the REST list endpoint — only collapsed in the web UI. Deleting + # would erase that durable checkpoint and re-qualify unchanged commits. A failed hide + # must never fail the job. + if gh api graphql -f query='mutation($id:ID!){minimizeComment(input:{subjectId:$id,classifier:RESOLVED}){minimizedComment{isMinimized}}}' -f id="$COMMENT_NODE_ID" --silent; then + echo "Hid /review rerun command comment ${COMMENT_NODE_ID} as resolved" + else + echo "::warning::Could not hide /review rerun command comment ${COMMENT_NODE_ID}" + fi + trigger-review: needs: match if: needs.match.outputs.matched == 'true' && needs.match.outputs.command == 'review' @@ -454,3 +476,23 @@ jobs: run: | . .github/scripts/shared/Update-AgentLabels.ps1 Clear-AgentReviewInProgress -PRNumber $env:PR_NUMBER -Owner '${{ github.repository_owner }}' -Repo '${{ github.event.repository.name }}' | Out-Null + + - name: Hide the /review command comment as resolved + if: github.event_name == 'issue_comment' && steps.trigger_azdo.outcome == 'success' + env: + GH_TOKEN: ${{ github.token }} + COMMENT_NODE_ID: ${{ github.event.comment.node_id }} + run: | + # Collapse only after the pipeline was actually triggered, so a lock-skip + # (locked == 'true') or a failed AzDO trigger leaves the /review comment visible + # for the user to retry. + # + # We MINIMIZE (hide as resolved) rather than delete so the command — and its + # --branch/--platform options — survives in the REST comment history that the rerun + # scanner replays. Minimized comments are still returned by the REST list endpoint; + # only collapsed in the web UI. A failed hide must never fail the review trigger. + if gh api graphql -f query='mutation($id:ID!){minimizeComment(input:{subjectId:$id,classifier:RESOLVED}){minimizedComment{isMinimized}}}' -f id="$COMMENT_NODE_ID" --silent; then + echo "Hid /review command comment ${COMMENT_NODE_ID} as resolved" + else + echo "::warning::Could not hide /review command comment ${COMMENT_NODE_ID}" + fi