PR Documentation Check #1118
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"3af66f1fd653245278baa8211a0584123c6fb4862729e4b304396a4d63d3ae83","compiler_version":"v0.72.0","strict":true,"agent_id":"copilot"} | |
| # gh-aw-manifest: {"version":1,"secrets":["ASPIRE_BOT_APP_ID","ASPIRE_BOT_PRIVATE_KEY","COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/create-github-app-token","sha":"1b10c78c7865c340bc4f6099eb2f838309f1e8c3","version":"v3.1.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"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":"ff0525b685481744f490a0d362753d8001e4b39d","version":"v0.72.0"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.41"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.41"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} | |
| # ___ _ _ | |
| # / _ \ | | (_) | |
| # | |_| | __ _ ___ _ __ | |_ _ ___ | |
| # | _ |/ _` |/ _ \ '_ \| __| |/ __| | |
| # | | | | (_| | __/ | | | |_| | (__ | |
| # \_| |_/\__, |\___|_| |_|\__|_|\___| | |
| # __/ | | |
| # _ _ |___/ | |
| # | | | | / _| | | |
| # | | | | ___ _ __ _ __| |_| | _____ ____ | |
| # | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___| | |
| # \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ | |
| # \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ | |
| # | |
| # This file was automatically generated by gh-aw (v0.72.0). DO NOT EDIT. | |
| # | |
| # To update this file, edit the corresponding .md file and run: | |
| # gh aw compile | |
| # Not all edits will cause changes to this file. | |
| # | |
| # For more information: https://github.github.com/gh-aw/introduction/overview/ | |
| # | |
| # Analyzes merged pull requests for significant user-facing changes. When a | |
| # PR is merged against main or release/* branches, this workflow determines | |
| # whether microsoft/aspire.dev needs a documentation PR. If documentation | |
| # updates are required, it creates a draft PR with the changes following the | |
| # doc-writer skill conventions. The draft PR targets the aspire.dev branch | |
| # resolved from the source PR's release reasoning (PR milestone, linked-issue | |
| # milestone, then source PR base), using the matching release/* branch when it | |
| # already exists and falling back to aspire.dev main otherwise. It also | |
| # comments on the original PR with a link to the draft PR (or a "no docs | |
| # needed" message). | |
| # | |
| # Secrets used: | |
| # - ASPIRE_BOT_APP_ID | |
| # - ASPIRE_BOT_PRIVATE_KEY | |
| # - COPILOT_GITHUB_TOKEN | |
| # - GH_AW_CI_TRIGGER_TOKEN | |
| # - GH_AW_GITHUB_MCP_SERVER_TOKEN | |
| # - GH_AW_GITHUB_TOKEN | |
| # - GITHUB_TOKEN | |
| # | |
| # Custom actions used: | |
| # - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| # - actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 | |
| # - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| # - actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 | |
| # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| # - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| # - github/gh-aw-actions/setup@ff0525b685481744f490a0d362753d8001e4b39d # v0.72.0 | |
| # | |
| # Container images used: | |
| # - ghcr.io/github/gh-aw-firewall/agent:0.25.41 | |
| # - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41 | |
| # - ghcr.io/github/gh-aw-firewall/squid:0.25.41 | |
| # - ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c | |
| # - ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959 | |
| # - node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f | |
| name: "PR Documentation Check" | |
| "on": | |
| pull_request: | |
| branches: | |
| - main | |
| - release/* | |
| types: | |
| - closed | |
| # stale-check: false # Stale-check processed as frontmatter hash check step in activation job | |
| workflow_dispatch: | |
| inputs: | |
| aw_context: | |
| default: "" | |
| description: Agent caller context (used internally by Agentic Workflows). | |
| required: false | |
| type: string | |
| pr_number: | |
| description: PR number to analyze | |
| required: true | |
| type: string | |
| permissions: {} | |
| concurrency: | |
| group: "gh-aw-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref || github.run_id }}" | |
| cancel-in-progress: true | |
| run-name: "PR Documentation Check" | |
| jobs: | |
| activation: | |
| needs: pre_activation | |
| if: > | |
| needs.pre_activation.outputs.activated == 'true' && (((github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch') && | |
| github.repository_owner == 'microsoft') && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id)) | |
| runs-on: ubuntu-slim | |
| permissions: | |
| contents: read | |
| outputs: | |
| body: ${{ steps.sanitized.outputs.body }} | |
| comment_id: "" | |
| comment_repo: "" | |
| engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} | |
| lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} | |
| model: ${{ steps.generate_aw_info.outputs.model }} | |
| secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} | |
| setup-trace-id: ${{ steps.setup.outputs.trace-id }} | |
| text: ${{ steps.sanitized.outputs.text }} | |
| title: ${{ steps.sanitized.outputs.title }} | |
| steps: | |
| - name: Setup Scripts | |
| id: setup | |
| uses: github/gh-aw-actions/setup@ff0525b685481744f490a0d362753d8001e4b39d # v0.72.0 | |
| with: | |
| destination: ${{ runner.temp }}/gh-aw/actions | |
| job-name: ${{ github.job }} | |
| trace-id: ${{ needs.pre_activation.outputs.setup-trace-id }} | |
| env: | |
| GH_AW_SETUP_WORKFLOW_NAME: "PR Documentation Check" | |
| GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/pr-docs-check.lock.yml@${{ github.ref }} | |
| GH_AW_INFO_VERSION: "1.0.40" | |
| - name: Generate agentic run info | |
| id: generate_aw_info | |
| env: | |
| GH_AW_INFO_ENGINE_ID: "copilot" | |
| GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" | |
| GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'claude-sonnet-4.6' }} | |
| GH_AW_INFO_VERSION: "1.0.40" | |
| GH_AW_INFO_AGENT_VERSION: "1.0.40" | |
| GH_AW_INFO_CLI_VERSION: "v0.72.0" | |
| GH_AW_INFO_WORKFLOW_NAME: "PR Documentation Check" | |
| GH_AW_INFO_EXPERIMENTAL: "false" | |
| GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" | |
| GH_AW_INFO_STAGED: "false" | |
| GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","github"]' | |
| GH_AW_INFO_FIREWALL_ENABLED: "true" | |
| GH_AW_INFO_AWF_VERSION: "v0.25.41" | |
| GH_AW_INFO_AWMG_VERSION: "" | |
| GH_AW_INFO_FIREWALL_TYPE: "squid" | |
| GH_AW_COMPILED_STRICT: "true" | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| with: | |
| script: | | |
| const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); | |
| setupGlobals(core, github, context, exec, io, getOctokit); | |
| const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs'); | |
| await main(core, context); | |
| - name: Validate COPILOT_GITHUB_TOKEN secret | |
| id: validate-secret | |
| run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh" COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default | |
| env: | |
| COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} | |
| - name: Checkout .github and .agents folders | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| sparse-checkout: | | |
| .github | |
| .agents | |
| .claude | |
| .codex | |
| .crush | |
| .gemini | |
| .opencode | |
| .pi | |
| sparse-checkout-cone-mode: true | |
| fetch-depth: 1 | |
| - name: Save agent config folders for base branch restoration | |
| env: | |
| GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode .pi" | |
| GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" | |
| # poutine:ignore untrusted_checkout_exec | |
| run: bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh" | |
| - name: Check compile-agentic version | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| GH_AW_COMPILED_VERSION: "v0.72.0" | |
| with: | |
| script: | | |
| const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); | |
| setupGlobals(core, github, context, exec, io, getOctokit); | |
| const { main } = require('${{ runner.temp }}/gh-aw/actions/check_version_updates.cjs'); | |
| await main(); | |
| - name: Compute current body text | |
| id: sanitized | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,docs.github.com,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" | |
| with: | |
| script: | | |
| const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); | |
| setupGlobals(core, github, context, exec, io, getOctokit); | |
| const { main } = require('${{ runner.temp }}/gh-aw/actions/compute_text.cjs'); | |
| await main(); | |
| - name: Create prompt with built-in context | |
| env: | |
| GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt | |
| GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl | |
| GH_AW_EXPR_2697560F: ${{ github.event.pull_request.number || github.event.inputs.pr_number }} | |
| GH_AW_GITHUB_ACTOR: ${{ github.actor }} | |
| GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} | |
| GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} | |
| GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} | |
| GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} | |
| GH_AW_GITHUB_EVENT_PULL_REQUEST_TITLE: ${{ github.event.pull_request.title }} | |
| GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} | |
| GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} | |
| GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} | |
| # poutine:ignore untrusted_checkout_exec | |
| run: | | |
| bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" | |
| { | |
| cat << 'GH_AW_PROMPT_9a91f417cffa3f44_EOF' | |
| <system> | |
| GH_AW_PROMPT_9a91f417cffa3f44_EOF | |
| cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" | |
| cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" | |
| cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" | |
| cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" | |
| cat << 'GH_AW_PROMPT_9a91f417cffa3f44_EOF' | |
| <safe-output-tools> | |
| Tools: create_pull_request, missing_tool, missing_data, noop, notify_source_pr | |
| GH_AW_PROMPT_9a91f417cffa3f44_EOF | |
| cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" | |
| cat << 'GH_AW_PROMPT_9a91f417cffa3f44_EOF' | |
| </safe-output-tools> | |
| GH_AW_PROMPT_9a91f417cffa3f44_EOF | |
| cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" | |
| cat << 'GH_AW_PROMPT_9a91f417cffa3f44_EOF' | |
| <github-context> | |
| The following GitHub context information is available for this workflow: | |
| {{#if __GH_AW_GITHUB_ACTOR__ }} | |
| - **actor**: __GH_AW_GITHUB_ACTOR__ | |
| {{/if}} | |
| {{#if __GH_AW_GITHUB_REPOSITORY__ }} | |
| - **repository**: __GH_AW_GITHUB_REPOSITORY__ | |
| {{/if}} | |
| {{#if __GH_AW_GITHUB_WORKSPACE__ }} | |
| - **workspace**: __GH_AW_GITHUB_WORKSPACE__ | |
| {{/if}} | |
| {{#if __GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ }} | |
| - **issue-number**: #__GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ | |
| {{/if}} | |
| {{#if __GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ }} | |
| - **discussion-number**: #__GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ | |
| {{/if}} | |
| {{#if __GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ }} | |
| - **pull-request-number**: #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ | |
| {{/if}} | |
| {{#if __GH_AW_GITHUB_EVENT_COMMENT_ID__ }} | |
| - **comment-id**: __GH_AW_GITHUB_EVENT_COMMENT_ID__ | |
| {{/if}} | |
| {{#if __GH_AW_GITHUB_RUN_ID__ }} | |
| - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__ | |
| {{/if}} | |
| - **checkouts**: The following repositories have been checked out and are available in the workspace: | |
| - `$GITHUB_WORKSPACE` → `microsoft/aspire.dev` (cwd) (**current** - this is the repository you are working on; use this as the target for all GitHub operations unless otherwise specified) [shallow clone, fetch-depth=1 (default)] [additional refs fetched: release/*] | |
| - `$GITHUB_WORKSPACE/_repos/aspire.dev` → `microsoft/aspire.dev` [shallow clone, fetch-depth=1 (default)] | |
| - **Note**: If a branch you need is not in the list above and is not listed as an additional fetched ref, it has NOT been checked out. For private repositories you cannot fetch it without proper authentication. If the branch is required and not available, exit with an error and ask the user to add it to the `fetch:` option of the `checkout:` configuration (e.g., `fetch: ["refs/pulls/open/*"]` for all open PR refs, or `fetch: ["main", "feature/my-branch"]` for specific branches). | |
| </github-context> | |
| GH_AW_PROMPT_9a91f417cffa3f44_EOF | |
| cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" | |
| cat << 'GH_AW_PROMPT_9a91f417cffa3f44_EOF' | |
| </system> | |
| {{#runtime-import .github/workflows/pr-docs-check.md}} | |
| GH_AW_PROMPT_9a91f417cffa3f44_EOF | |
| } > "$GH_AW_PROMPT" | |
| - name: Interpolate variables and render templates | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt | |
| GH_AW_ENGINE_ID: "copilot" | |
| GH_AW_EXPR_2697560F: ${{ github.event.pull_request.number || github.event.inputs.pr_number }} | |
| GH_AW_GITHUB_EVENT_PULL_REQUEST_TITLE: ${{ github.event.pull_request.title }} | |
| with: | |
| script: | | |
| const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); | |
| setupGlobals(core, github, context, exec, io, getOctokit); | |
| const { main } = require('${{ runner.temp }}/gh-aw/actions/interpolate_prompt.cjs'); | |
| await main(); | |
| - name: Substitute placeholders | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt | |
| GH_AW_EXPR_2697560F: ${{ github.event.pull_request.number || github.event.inputs.pr_number }} | |
| GH_AW_GITHUB_ACTOR: ${{ github.actor }} | |
| GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} | |
| GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} | |
| GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} | |
| GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} | |
| GH_AW_GITHUB_EVENT_PULL_REQUEST_TITLE: ${{ github.event.pull_request.title }} | |
| GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} | |
| GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} | |
| GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} | |
| GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' | |
| GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: ${{ needs.pre_activation.outputs.activated }} | |
| with: | |
| script: | | |
| const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); | |
| setupGlobals(core, github, context, exec, io, getOctokit); | |
| const substitutePlaceholders = require('${{ runner.temp }}/gh-aw/actions/substitute_placeholders.cjs'); | |
| // Call the substitution function | |
| return await substitutePlaceholders({ | |
| file: process.env.GH_AW_PROMPT, | |
| substitutions: { | |
| GH_AW_EXPR_2697560F: process.env.GH_AW_EXPR_2697560F, | |
| GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR, | |
| GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID, | |
| GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER, | |
| GH_AW_GITHUB_EVENT_ISSUE_NUMBER: process.env.GH_AW_GITHUB_EVENT_ISSUE_NUMBER, | |
| GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER, | |
| GH_AW_GITHUB_EVENT_PULL_REQUEST_TITLE: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_TITLE, | |
| GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, | |
| GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, | |
| GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, | |
| GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST, | |
| GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED | |
| } | |
| }); | |
| - name: Validate prompt placeholders | |
| env: | |
| GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt | |
| # poutine:ignore untrusted_checkout_exec | |
| run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh" | |
| - name: Print prompt | |
| env: | |
| GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt | |
| # poutine:ignore untrusted_checkout_exec | |
| run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh" | |
| - name: Upload activation artifact | |
| if: success() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: activation | |
| include-hidden-files: true | |
| path: | | |
| /tmp/gh-aw/aw_info.json | |
| /tmp/gh-aw/aw-prompts/prompt.txt | |
| /tmp/gh-aw/aw-prompts/prompt-template.txt | |
| /tmp/gh-aw/aw-prompts/prompt-import-tree.json | |
| /tmp/gh-aw/github_rate_limits.jsonl | |
| /tmp/gh-aw/base | |
| /tmp/gh-aw/.github/agents | |
| if-no-files-found: ignore | |
| retention-days: 1 | |
| agent: | |
| needs: activation | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| issues: read | |
| pull-requests: read | |
| env: | |
| DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} | |
| GH_AW_ASSETS_ALLOWED_EXTS: "" | |
| GH_AW_ASSETS_BRANCH: "" | |
| GH_AW_ASSETS_MAX_SIZE_KB: 0 | |
| GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs | |
| GH_AW_WORKFLOW_ID_SANITIZED: prdocscheck | |
| outputs: | |
| agentic_engine_timeout: ${{ steps.detect-copilot-errors.outputs.agentic_engine_timeout || 'false' }} | |
| checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} | |
| effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} | |
| has_patch: ${{ steps.collect_output.outputs.has_patch }} | |
| inference_access_error: ${{ steps.detect-copilot-errors.outputs.inference_access_error || 'false' }} | |
| mcp_policy_error: ${{ steps.detect-copilot-errors.outputs.mcp_policy_error || 'false' }} | |
| model: ${{ needs.activation.outputs.model }} | |
| model_not_supported_error: ${{ steps.detect-copilot-errors.outputs.model_not_supported_error || 'false' }} | |
| output: ${{ steps.collect_output.outputs.output }} | |
| output_types: ${{ steps.collect_output.outputs.output_types }} | |
| setup-trace-id: ${{ steps.setup.outputs.trace-id }} | |
| steps: | |
| - name: Setup Scripts | |
| id: setup | |
| uses: github/gh-aw-actions/setup@ff0525b685481744f490a0d362753d8001e4b39d # v0.72.0 | |
| with: | |
| destination: ${{ runner.temp }}/gh-aw/actions | |
| job-name: ${{ github.job }} | |
| trace-id: ${{ needs.activation.outputs.setup-trace-id }} | |
| env: | |
| GH_AW_SETUP_WORKFLOW_NAME: "PR Documentation Check" | |
| GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/pr-docs-check.lock.yml@${{ github.ref }} | |
| GH_AW_INFO_VERSION: "1.0.40" | |
| - name: Set runtime paths | |
| id: set-runtime-paths | |
| run: | | |
| { | |
| echo "GH_AW_SAFE_OUTPUTS=${RUNNER_TEMP}/gh-aw/safeoutputs/outputs.jsonl" | |
| echo "GH_AW_SAFE_OUTPUTS_CONFIG_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" | |
| echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Generate GitHub App token for checkout (0) | |
| id: checkout-app-token-0 | |
| uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 | |
| with: | |
| client-id: ${{ secrets.ASPIRE_BOT_APP_ID }} | |
| private-key: ${{ secrets.ASPIRE_BOT_PRIVATE_KEY }} | |
| owner: microsoft | |
| repositories: aspire.dev | |
| github-api-url: ${{ github.api_url }} | |
| permission-contents: read | |
| permission-issues: read | |
| permission-pull-requests: read | |
| - name: Generate GitHub App token for checkout (1) | |
| id: checkout-app-token-1 | |
| uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 | |
| with: | |
| client-id: ${{ secrets.ASPIRE_BOT_APP_ID }} | |
| private-key: ${{ secrets.ASPIRE_BOT_PRIVATE_KEY }} | |
| owner: microsoft | |
| repositories: aspire.dev | |
| github-api-url: ${{ github.api_url }} | |
| permission-contents: read | |
| permission-issues: read | |
| permission-pull-requests: read | |
| - name: Checkout repository | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Checkout microsoft/aspire.dev | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| repository: microsoft/aspire.dev | |
| token: ${{ steps.checkout-app-token-0.outputs.token }} | |
| - name: Fetch additional refs for microsoft/aspire.dev | |
| env: | |
| GH_AW_FETCH_TOKEN: ${{ steps.checkout-app-token-0.outputs.token }} | |
| run: | | |
| header=$(printf "x-access-token:%s" "${GH_AW_FETCH_TOKEN}" | base64 -w 0) | |
| git -c "http.extraheader=Authorization: Basic ${header}" fetch origin '+refs/heads/release/*:refs/remotes/origin/release/*' | |
| - name: Checkout microsoft/aspire.dev into _repos/aspire.dev | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| repository: microsoft/aspire.dev | |
| path: _repos/aspire.dev | |
| token: ${{ steps.checkout-app-token-1.outputs.token }} | |
| - name: Create gh-aw temp directory | |
| run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" | |
| - name: Configure gh CLI for GitHub Enterprise | |
| run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh" | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| - name: Configure Git credentials | |
| env: | |
| REPO_NAME: ${{ github.repository }} | |
| SERVER_URL: ${{ github.server_url }} | |
| GITHUB_TOKEN: ${{ github.token }} | |
| run: | | |
| git config --global user.email "github-actions[bot]@users.noreply.github.com" | |
| git config --global user.name "github-actions[bot]" | |
| git config --global am.keepcr true | |
| # Re-authenticate git with GitHub token | |
| SERVER_URL_STRIPPED="${SERVER_URL#https://}" | |
| git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" | |
| echo "Git configured with standard GitHub Actions identity" | |
| - name: Checkout PR branch | |
| id: checkout-pr | |
| if: | | |
| github.event.pull_request || github.event.issue.pull_request | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} | |
| with: | |
| github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} | |
| script: | | |
| const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); | |
| setupGlobals(core, github, context, exec, io, getOctokit); | |
| const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); | |
| await main(); | |
| - name: Install GitHub Copilot CLI | |
| run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.40 | |
| env: | |
| GH_HOST: github.com | |
| - name: Install AWF binary | |
| run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.41 | |
| - name: Generate GitHub App token | |
| id: github-mcp-app-token | |
| uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 | |
| with: | |
| client-id: ${{ secrets.ASPIRE_BOT_APP_ID }} | |
| private-key: ${{ secrets.ASPIRE_BOT_PRIVATE_KEY }} | |
| owner: microsoft | |
| repositories: |- | |
| aspire.dev | |
| aspire | |
| github-api-url: ${{ github.api_url }} | |
| permission-contents: read | |
| permission-issues: read | |
| permission-pull-requests: read | |
| - name: Parse integrity filter lists | |
| id: parse-guard-vars | |
| env: | |
| GH_AW_BLOCKED_USERS_VAR: ${{ vars.GH_AW_GITHUB_BLOCKED_USERS || '' }} | |
| GH_AW_TRUSTED_USERS_VAR: ${{ vars.GH_AW_GITHUB_TRUSTED_USERS || '' }} | |
| GH_AW_APPROVAL_LABELS_VAR: ${{ vars.GH_AW_GITHUB_APPROVAL_LABELS || '' }} | |
| run: bash "${RUNNER_TEMP}/gh-aw/actions/parse_guard_list.sh" | |
| - name: Download activation artifact | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: activation | |
| path: /tmp/gh-aw | |
| - name: Restore agent config folders from base branch | |
| if: steps.checkout-pr.outcome == 'success' | |
| env: | |
| GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode .pi" | |
| GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" | |
| run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh" | |
| - name: Restore inline sub-agents from activation artifact | |
| env: | |
| GH_AW_SUB_AGENT_DIR: ".github/agents" | |
| GH_AW_SUB_AGENT_EXT: ".agent.md" | |
| run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_sub_agents.sh" | |
| - id: resolve-target-app-token | |
| name: Mint app token for target-branch resolver | |
| uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 | |
| with: | |
| app-id: ${{ secrets.ASPIRE_BOT_APP_ID }} | |
| owner: microsoft | |
| private-key: ${{ secrets.ASPIRE_BOT_PRIVATE_KEY }} | |
| repositories: | | |
| aspire | |
| aspire.dev | |
| - env: | |
| GH_TOKEN: ${{ steps.resolve-target-app-token.outputs.token }} | |
| PR_NUMBER: ${{ github.event.pull_request.number || github.event.inputs.pr_number }} | |
| name: Resolve target aspire.dev branch | |
| run: "set -euo pipefail\n\nmkdir -p .pr-docs-check\nOUT=.pr-docs-check/target.json\n\nif [ -z \"${PR_NUMBER}\" ]; then\n echo \"ERROR: PR_NUMBER is empty; cannot resolve target branch.\" >&2\n exit 1\nfi\n# PR_NUMBER reaches this script either from `github.event.pull_request.number`\n# (already an integer at the GitHub Actions layer) or from the\n# `workflow_dispatch` `pr_number` string input (free-form, supplied by a\n# maintainer). Reject anything that isn't a positive integer up front so\n# downstream `jq --argjson` and `gh api` calls produce a clear error\n# instead of an opaque parse failure.\nif ! [[ \"${PR_NUMBER}\" =~ ^[1-9][0-9]*$ ]]; then\n echo \"ERROR: PR_NUMBER '${PR_NUMBER}' is not a positive integer.\" >&2\n exit 1\nfi\n\necho \"Resolving target microsoft/aspire.dev branch for microsoft/aspire#${PR_NUMBER}\"\n# --- 1. Fetch source PR metadata from microsoft/aspire ---------------\n# We need the PR's own milestone, base branch, and body. The body is\n# later scanned for `Closes/Fixes/Resolves #N` linked-issue refs.\nPR_JSON=\"$(mktemp)\"\ngh api \"/repos/microsoft/aspire/pulls/${PR_NUMBER}\" > \"${PR_JSON}\"\n\nPR_MILESTONE_TITLE=\"$(jq -r '.milestone.title // empty' \"${PR_JSON}\")\"\nPR_BASE_REF=\"$(jq -r '.base.ref // empty' \"${PR_JSON}\")\"\nPR_BODY=\"$(jq -r '.body // \"\"' \"${PR_JSON}\")\"\n\necho \"PR milestone : '${PR_MILESTONE_TITLE}'\"\necho \"PR base ref : '${PR_BASE_REF}'\"\n\n# --- 2. Extract linked issue numbers from the PR body ----------------\n# GitHub recognizes these closing keywords (case-insensitive), with an\n# optional `:` after the keyword, optional `owner/repo`, and a `#N`:\n#\n# close, closes, closed,\n# fix, fixes, fixed,\n# resolve, resolves, resolved\n#\n# Examples it must accept:\n# Fixes #123\n# Fixes: #123\n# Closes microsoft/aspire#456\n# Resolves: microsoft/aspire#789\n#\n# See:\n# https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword\n#\n# Only same-repo refs (no owner/repo, or `microsoft/aspire`) are kept;\n# cross-repo references in linked issues don't help us pick a docs\n# release branch.\n#\n# Implemented in python3 (preinstalled on ubuntu-latest) because POSIX\n# grep -E lacks reliable case-insensitive multiline matching with the\n# exact keyword set we need. We deliberately do NOT swallow parser\n# errors with `|| true`: if python3 fails (e.g., missing on the runner,\n# body too large to pass as argv), the resolver should fail loudly\n# rather than silently pick the wrong branch from an empty linked-issue\n# set.\nLINKED_FILE=\"$(mktemp)\"\n: > \"${LINKED_FILE}\"\npython3 - \"${PR_BODY}\" > \"${LINKED_FILE}\" <<'PY'\nimport re, sys\nbody = sys.argv[1] if len(sys.argv) > 1 else \"\"\npat = re.compile(\n r'\\b(close[sd]?|fix(?:es|ed)?|resolve[sd]?)\\s*:?\\s+'\n r'(?:([A-Za-z0-9._-]+/[A-Za-z0-9._-]+))?#(\\d+)\\b',\n re.IGNORECASE,\n)\nseen = set()\nfor m in pat.finditer(body):\n repo = (m.group(2) or \"microsoft/aspire\").lower()\n if repo != \"microsoft/aspire\":\n continue\n n = m.group(3)\n if n in seen:\n continue\n seen.add(n)\n print(n)\nPY\n\nLINKED_ISSUES=()\nwhile IFS= read -r n; do\n [ -n \"${n}\" ] && LINKED_ISSUES+=(\"${n}\")\ndone < \"${LINKED_FILE}\"\nrm -f \"${LINKED_FILE}\"\n\nif [ \"${#LINKED_ISSUES[@]}\" -gt 0 ]; then\n echo \"Linked issues : ${LINKED_ISSUES[*]}\"\nelse\n echo \"Linked issues : <none>\"\nfi\n\n# --- 3. Fetch milestone for each linked issue ------------------------\n# Stop at the first non-empty milestone for the priority resolution\n# below, but record every issue's milestone (or null) for observability.\nLINKED_ISSUE_MILESTONE_TITLE=\"\"\nLINKED_ARR=()\nfor n in \"${LINKED_ISSUES[@]:-}\"; do\n [ -z \"${n}\" ] && continue\n ISSUE_JSON=\"$(mktemp)\"\n # A linked-issue ref may be deleted, transferred, or otherwise\n # unreadable. Treat fetch failure as \"no milestone\" rather than\n # aborting the entire workflow.\n if gh api \"/repos/microsoft/aspire/issues/${n}\" > \"${ISSUE_JSON}\" 2>/dev/null; then\n m=\"$(jq -r '.milestone.title // empty' \"${ISSUE_JSON}\")\"\n else\n m=\"\"\n echo \" WARN: could not fetch microsoft/aspire#${n}; treating milestone as empty\"\n fi\n rm -f \"${ISSUE_JSON}\"\n LINKED_ARR+=(\"$(jq -n --arg num \"${n}\" --arg ms \"${m}\" \\\n '{number: ($num|tonumber), milestone: (if ($ms|length)>0 then $ms else null end)}')\")\n if [ -z \"${LINKED_ISSUE_MILESTONE_TITLE}\" ] && [ -n \"${m}\" ]; then\n LINKED_ISSUE_MILESTONE_TITLE=\"${m}\"\n fi\ndone\n\nif [ \"${#LINKED_ARR[@]}\" -gt 0 ]; then\n LINKED_ISSUES_JSON=\"$(printf '%s\\n' \"${LINKED_ARR[@]}\" | jq -s '.')\"\nelse\n LINKED_ISSUES_JSON=\"[]\"\nfi\n\n# --- 4. Pick a candidate target branch -------------------------------\n# Priority (first match wins):\n# 1. PR milestone title (normalized)\n# 2. Linked-issue milestone title (normalized)\n# 3. PR base branch if it matches release/X.Y[.Z]\n# 4. main\n#\n# Milestone normalization: `^v?(\\d+)\\.(\\d+)(?:\\.(\\d+))?` matched as a\n# prefix, so titles like \"13.3\", \"v13.3\", \"13.3 - Preview 1\" all map\n# to release/13.3, and \"13.2.1\" maps to release/13.2.1.\nnormalize_milestone() {\n local title=\"$1\"\n local m\n m=\"$(printf '%s' \"${title}\" | grep -oE '^v?[0-9]+\\.[0-9]+(\\.[0-9]+)?' | head -n1 || true)\"\n if [ -z \"${m}\" ]; then\n return 1\n fi\n m=\"${m#v}\"\n printf 'release/%s' \"${m}\"\n}\n\nCANDIDATE=\"\"\nCANDIDATE_SOURCE=\"\"\nCANDIDATE_SOURCE_DETAIL=\"\"\n\nif [ -z \"${CANDIDATE}\" ] && [ -n \"${PR_MILESTONE_TITLE}\" ]; then\n if c=\"$(normalize_milestone \"${PR_MILESTONE_TITLE}\")\"; then\n CANDIDATE=\"${c}\"\n CANDIDATE_SOURCE=\"pr_milestone\"\n CANDIDATE_SOURCE_DETAIL=\"${PR_MILESTONE_TITLE}\"\n fi\nfi\n\nif [ -z \"${CANDIDATE}\" ] && [ -n \"${LINKED_ISSUE_MILESTONE_TITLE}\" ]; then\n if c=\"$(normalize_milestone \"${LINKED_ISSUE_MILESTONE_TITLE}\")\"; then\n CANDIDATE=\"${c}\"\n CANDIDATE_SOURCE=\"linked_issue_milestone\"\n CANDIDATE_SOURCE_DETAIL=\"${LINKED_ISSUE_MILESTONE_TITLE}\"\n fi\nfi\n\nif [ -z \"${CANDIDATE}\" ] && [[ \"${PR_BASE_REF}\" =~ ^release/[0-9]+\\.[0-9]+(\\.[0-9]+)?$ ]]; then\n CANDIDATE=\"${PR_BASE_REF}\"\n CANDIDATE_SOURCE=\"pr_base\"\n CANDIDATE_SOURCE_DETAIL=\"${PR_BASE_REF}\"\nfi\n\nif [ -z \"${CANDIDATE}\" ]; then\n CANDIDATE=\"main\"\n CANDIDATE_SOURCE=\"fallback_main\"\n CANDIDATE_SOURCE_DETAIL=\"no milestone, linked-issue milestone, or release/* base ref resolved\"\nfi\n\necho \"Candidate : ${CANDIDATE} (source: ${CANDIDATE_SOURCE})\"\n\n# --- 5. Enumerate release/* branches on microsoft/aspire.dev ---------\n# Primary: local git on the current workspace, which is checked out at\n# microsoft/aspire.dev with `release/*` refs fetched into\n# `refs/remotes/origin/release/*` via the workflow `checkout:` block.\n#\n# Fallback: `gh api /repos/microsoft/aspire.dev/branches` paginated.\n# Used if the local fetch produced nothing (e.g., no release branches\n# have been pushed yet, or the fetch silently failed). The GH_TOKEN\n# used here is the aspire-bot installation token minted at the top of\n# `pre-agent-steps`, which has explicit `contents: read` on both\n# microsoft/aspire and microsoft/aspire.dev — the default GITHUB_TOKEN\n# is scoped only to the workflow's own repo and cannot read aspire.dev's\n# branch list reliably.\nENUMERATION_SOURCE=\"\"\nRELEASE_BRANCHES_FILE=\"$(mktemp)\"\n: > \"${RELEASE_BRANCHES_FILE}\"\n\nif git -C \"${GITHUB_WORKSPACE}\" rev-parse --is-inside-work-tree >/dev/null 2>&1; then\n git -C \"${GITHUB_WORKSPACE}\" for-each-ref \\\n --format='%(refname:short)' 'refs/remotes/origin/release/*' \\\n | sed 's|^origin/||' > \"${RELEASE_BRANCHES_FILE}\" || true\nfi\n\nif [ -s \"${RELEASE_BRANCHES_FILE}\" ]; then\n ENUMERATION_SOURCE=\"git\"\nelse\n echo \"Local git enumeration returned no release/* branches; falling back to gh api\"\n if gh api --paginate \"/repos/microsoft/aspire.dev/branches?per_page=100\" \\\n | jq -r '.[].name | select(startswith(\"release/\"))' \\\n > \"${RELEASE_BRANCHES_FILE}\" 2>/dev/null; then\n ENUMERATION_SOURCE=\"gh_api\"\n else\n echo \" WARN: gh api fallback for aspire.dev branches failed; treating list as empty\"\n : > \"${RELEASE_BRANCHES_FILE}\"\n ENUMERATION_SOURCE=\"empty\"\n fi\nfi\n\n# De-duplicate and sort so the JSON output is deterministic across runs.\nsort -u -o \"${RELEASE_BRANCHES_FILE}\" \"${RELEASE_BRANCHES_FILE}\"\n\nAVAILABLE_BRANCHES_JSON=\"$(jq -R -s 'split(\"\\n\") | map(select(length > 0))' \"${RELEASE_BRANCHES_FILE}\")\"\necho \"Release branches on aspire.dev (source=${ENUMERATION_SOURCE}):\"\nif [ -s \"${RELEASE_BRANCHES_FILE}\" ]; then\n sed 's/^/ - /' \"${RELEASE_BRANCHES_FILE}\"\nelse\n echo \" <none>\"\nfi\n\n# --- 6. Compute the effective target branch --------------------------\n# Policy (in priority order):\n# 1. exact_match: CANDIDATE is a release/* branch that exists on\n# aspire.dev → use it as-is.\n# 2. latest_release_fallback: aspire.dev has at least one release/*\n# branch → use the highest-versioned one. This covers both:\n# - CANDIDATE was `main` (no milestone/linked-issue/base-ref\n# signal) — docs for upcoming-release work should still land\n# on the staged release/* branch, not on aspire.dev's main.\n# - CANDIDATE was a release/* (e.g. release/13.3) that no\n# longer exists on aspire.dev (already shipped, branch\n# deleted). The docs site only keeps a release/* branch for\n# the upcoming release; older release-branch content is\n# merged into main on aspire.dev as those releases ship.\n# 3. main_fallback: aspire.dev has no release/* at all → use main.\n#\n# Sort release branches with sort -V *after* stripping the\n# \"release/\" prefix so the numeric version compares cleanly:\n# \"release/13.4\" beats \"release/9.5\" (without -V, lexicographic\n# ordering would put 9.5 last; with -V on the bare versions, 13.4\n# comes last).\nLATEST_RELEASE=\"\"\nif [ -s \"${RELEASE_BRANCHES_FILE}\" ]; then\n LATEST_RELEASE=\"$(\n sed -n 's|^release/||p' \"${RELEASE_BRANCHES_FILE}\" \\\n | sort -V \\\n | tail -n1 \\\n | sed 's|^|release/|'\n )\"\nfi\n\nEFFECTIVE=\"\"\nRESOLUTION=\"\"\nif [ \"${CANDIDATE}\" != \"main\" ] && grep -Fxq \"${CANDIDATE}\" \"${RELEASE_BRANCHES_FILE}\"; then\n EFFECTIVE=\"${CANDIDATE}\"\n RESOLUTION=\"exact_match\"\nelif [ -n \"${LATEST_RELEASE}\" ]; then\n EFFECTIVE=\"${LATEST_RELEASE}\"\n RESOLUTION=\"latest_release_fallback\"\n if [ \"${CANDIDATE}\" = \"main\" ]; then\n echo \"Candidate was main; using latest aspire.dev release branch ${EFFECTIVE}\"\n else\n echo \"Candidate ${CANDIDATE} not present on microsoft/aspire.dev; using latest release branch ${EFFECTIVE} instead\"\n fi\nelse\n EFFECTIVE=\"main\"\n RESOLUTION=\"main_fallback\"\n echo \"No release/* branches on microsoft/aspire.dev; falling back to main\"\nfi\n\nrm -f \"${RELEASE_BRANCHES_FILE}\" \"${PR_JSON}\"\n\necho \"Effective : ${EFFECTIVE} (resolution=${RESOLUTION})\"\n\n# --- 7. Emit target.json ---------------------------------------------\njq -n \\\n --argjson pr_number \"${PR_NUMBER}\" \\\n --arg pr_base_ref \"${PR_BASE_REF}\" \\\n --arg candidate \"${CANDIDATE}\" \\\n --arg candidate_source \"${CANDIDATE_SOURCE}\" \\\n --arg candidate_source_detail \"${CANDIDATE_SOURCE_DETAIL}\" \\\n --arg effective \"${EFFECTIVE}\" \\\n --arg resolution \"${RESOLUTION}\" \\\n --argjson available \"${AVAILABLE_BRANCHES_JSON}\" \\\n --arg enumeration_source \"${ENUMERATION_SOURCE}\" \\\n --argjson linked_issues \"${LINKED_ISSUES_JSON}\" \\\n '{\n source_pr_number: $pr_number,\n source_pr_base_ref: $pr_base_ref,\n candidate_target_branch: $candidate,\n candidate_source: $candidate_source,\n candidate_source_detail: $candidate_source_detail,\n effective_target_branch: $effective,\n target_resolution: $resolution,\n available_release_branches: $available,\n enumeration_source: $enumeration_source,\n linked_issues: $linked_issues\n }' > \"${OUT}\"\n\necho \"--- ${OUT} ---\"\ncat \"${OUT}\"\n" | |
| - name: Check out signal-computation script | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| path: _repos/aspire | |
| repository: microsoft/aspire | |
| sparse-checkout: | | |
| .github/workflows/pr-docs-check | |
| sparse-checkout-cone-mode: false | |
| - env: | |
| GH_TOKEN: ${{ steps.resolve-target-app-token.outputs.token }} | |
| PR_NUMBER: ${{ github.event.pull_request.number || github.event.inputs.pr_number }} | |
| name: Compute user-facing signals | |
| run: "set -euo pipefail\n\nmkdir -p .pr-docs-check\nOUT=.pr-docs-check/signals.json\n\nif [ -z \"${PR_NUMBER}\" ]; then\n echo \"ERROR: PR_NUMBER is empty; cannot compute signals.\" >&2\n exit 1\nfi\n# Same input validation as the target-branch resolver above — reject\n# anything that isn't a positive integer so a bad `workflow_dispatch`\n# input fails loudly here rather than producing an opaque gh api error.\nif ! [[ \"${PR_NUMBER}\" =~ ^[1-9][0-9]*$ ]]; then\n echo \"ERROR: PR_NUMBER '${PR_NUMBER}' is not a positive integer.\" >&2\n exit 1\nfi\n\necho \"Computing user-facing signals for microsoft/aspire#${PR_NUMBER}\"\n\n# --- 1. Fetch PR metadata (body) and changed files (paths + patches) -\n# The files endpoint returns at most 30 files per page and is capped\n# at 300 files / 3000 lines total per the GitHub API; that's fine\n# for our purposes because signal detection only needs path patterns\n# and patch hunks, and any change large enough to overflow that cap\n# is almost certainly user-facing on path patterns alone. The\n# script also emits `diff_scan_skipped_due_to_missing_patch` when\n# the API omits a `patch` for a file matched by a diff trigger,\n# so very-large diffs gate conservatively.\n#\n# https://docs.github.com/en/rest/pulls/pulls#list-pull-requests-files\nPR_JSON=\"$(mktemp)\"\nFILES_JSON=\"$(mktemp)\"\ngh api \"/repos/microsoft/aspire/pulls/${PR_NUMBER}\" > \"${PR_JSON}\"\ngh api --paginate \"/repos/microsoft/aspire/pulls/${PR_NUMBER}/files?per_page=100\" \\\n | jq -s 'add // []' > \"${FILES_JSON}\"\n\nFILE_COUNT=\"$(jq 'length' \"${FILES_JSON}\")\"\necho \"Files in PR : ${FILE_COUNT}\"\n\n# --- 2. Run signal detection ----------------------------------------\n# The signal catalog lives in a standalone Python script (preinstalled\n# on ubuntu-latest) so a malformed regex fails the step loudly instead\n# of being silently absorbed by a shell pipeline, the catalog can be\n# reviewed with syntax highlighting, and it has its own unittest suite.\npython3 _repos/aspire/.github/workflows/pr-docs-check/compute_signals.py \\\n \"${PR_JSON}\" \"${FILES_JSON}\" \"${OUT}\"\n\nrm -f \"${PR_JSON}\" \"${FILES_JSON}\"\n\necho \"--- ${OUT} ---\"\ncat \"${OUT}\"\n" | |
| - name: Download container images | |
| run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.41 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41 ghcr.io/github/gh-aw-firewall/squid:0.25.41 ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959 node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f | |
| - name: Generate Safe Outputs Config | |
| run: | | |
| mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" | |
| mkdir -p /tmp/gh-aw/safeoutputs | |
| mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs | |
| cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_9fe4e8b345f8b35b_EOF' | |
| {"create_pull_request":{"allowed_base_branches":["main","release/*"],"base_branch":"main","draft":true,"fallback_as_issue":true,"labels":["docs-from-code"],"max":1,"max_patch_files":100,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"protected_files_policy":"blocked","target-repo":"microsoft/aspire.dev","title_prefix":"[docs] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"notify-source-pr":{"description":"Post the documentation analysis result as a comment on the source\nmicrosoft/aspire pull request and (when a draft documentation PR was\nopened on microsoft/aspire.dev) request a review from the SME\nidentified from the source PR.\n\nEmit exactly one `notify_source_pr` item per run, after you've finished\nany `create_pull_request` or no-docs-needed reasoning. Use `result:\n\"drafted\"` when you just emitted a `create_pull_request`; use `result:\n\"skipped\"` when no docs PR is needed. DO NOT try to embed the drafted\nPR's URL or number in the `summary` text — the workflow knows them\nfrom the safe-outputs handler and will substitute the real values.\n","inputs":{"result":{"default":null,"description":"'drafted' if a docs PR was opened on microsoft/aspire.dev, or 'skipped' if no docs PR was needed.","required":true,"type":"string"},"sme_login":{"default":null,"description":"GitHub login of the SME from the source PR (preferred reviewer for the drafted docs PR). Empty string if no SME was identified.","required":false,"type":"string"},"source_pr_number":{"default":null,"description":"PR number on microsoft/aspire that triggered this run.","required":true,"type":"number"},"summary":{"default":null,"description":"Short markdown summary of the documentation changes (when drafted) or rationale (when skipped). 1-3 sentences plus optional bullet list. DO NOT include the drafted PR URL or number — the workflow injects those automatically.","required":true,"type":"string"},"target_branch":{"default":null,"description":"Effective target branch on microsoft/aspire.dev (only meaningful when result is 'drafted').","required":false,"type":"string"}}},"report_incomplete":{}} | |
| GH_AW_SAFE_OUTPUTS_CONFIG_9fe4e8b345f8b35b_EOF | |
| - name: Generate Safe Outputs Tools | |
| env: | |
| GH_AW_TOOLS_META_JSON: | | |
| { | |
| "description_suffixes": { | |
| "create_pull_request": " CONSTRAINTS: Maximum 1 pull request(s) can be created. Title will be prefixed with \"[docs] \". Labels [\"docs-from-code\"] will be automatically added. PRs will be created as drafts." | |
| }, | |
| "repo_params": {}, | |
| "dynamic_tools": [ | |
| { | |
| "description": "Post the documentation analysis result as a comment on the source\nmicrosoft/aspire pull request and (when a draft documentation PR was\nopened on microsoft/aspire.dev) request a review from the SME\nidentified from the source PR.\n\nEmit exactly one `notify_source_pr` item per run, after you've finished\nany `create_pull_request` or no-docs-needed reasoning. Use `result:\n\"drafted\"` when you just emitted a `create_pull_request`; use `result:\n\"skipped\"` when no docs PR is needed. DO NOT try to embed the drafted\nPR's URL or number in the `summary` text — the workflow knows them\nfrom the safe-outputs handler and will substitute the real values.\n", | |
| "inputSchema": { | |
| "additionalProperties": false, | |
| "properties": { | |
| "result": { | |
| "description": "'drafted' if a docs PR was opened on microsoft/aspire.dev, or 'skipped' if no docs PR was needed.", | |
| "type": "string" | |
| }, | |
| "sme_login": { | |
| "description": "GitHub login of the SME from the source PR (preferred reviewer for the drafted docs PR). Empty string if no SME was identified.", | |
| "type": "string" | |
| }, | |
| "source_pr_number": { | |
| "description": "PR number on microsoft/aspire that triggered this run.", | |
| "type": "number" | |
| }, | |
| "summary": { | |
| "description": "Short markdown summary of the documentation changes (when drafted) or rationale (when skipped). 1-3 sentences plus optional bullet list. DO NOT include the drafted PR URL or number — the workflow injects those automatically.", | |
| "type": "string" | |
| }, | |
| "target_branch": { | |
| "description": "Effective target branch on microsoft/aspire.dev (only meaningful when result is 'drafted').", | |
| "type": "string" | |
| } | |
| }, | |
| "required": [ | |
| "result", | |
| "source_pr_number", | |
| "summary" | |
| ], | |
| "type": "object" | |
| }, | |
| "name": "notify_source_pr" | |
| } | |
| ] | |
| } | |
| GH_AW_VALIDATION_JSON: | | |
| { | |
| "create_pull_request": { | |
| "defaultMax": 1, | |
| "fields": { | |
| "base": { | |
| "type": "string", | |
| "sanitize": true, | |
| "maxLength": 128 | |
| }, | |
| "body": { | |
| "required": true, | |
| "type": "string", | |
| "sanitize": true, | |
| "maxLength": 65000 | |
| }, | |
| "branch": { | |
| "required": true, | |
| "type": "string", | |
| "sanitize": true, | |
| "maxLength": 256 | |
| }, | |
| "draft": { | |
| "type": "boolean" | |
| }, | |
| "labels": { | |
| "type": "array", | |
| "itemType": "string", | |
| "itemSanitize": true, | |
| "itemMaxLength": 128 | |
| }, | |
| "repo": { | |
| "type": "string", | |
| "maxLength": 256 | |
| }, | |
| "title": { | |
| "required": true, | |
| "type": "string", | |
| "sanitize": true, | |
| "maxLength": 128 | |
| } | |
| } | |
| }, | |
| "missing_data": { | |
| "defaultMax": 20, | |
| "fields": { | |
| "alternatives": { | |
| "type": "string", | |
| "sanitize": true, | |
| "maxLength": 256 | |
| }, | |
| "context": { | |
| "type": "string", | |
| "sanitize": true, | |
| "maxLength": 256 | |
| }, | |
| "data_type": { | |
| "type": "string", | |
| "sanitize": true, | |
| "maxLength": 128 | |
| }, | |
| "reason": { | |
| "type": "string", | |
| "sanitize": true, | |
| "maxLength": 256 | |
| } | |
| } | |
| }, | |
| "missing_tool": { | |
| "defaultMax": 20, | |
| "fields": { | |
| "alternatives": { | |
| "type": "string", | |
| "sanitize": true, | |
| "maxLength": 512 | |
| }, | |
| "reason": { | |
| "required": true, | |
| "type": "string", | |
| "sanitize": true, | |
| "maxLength": 256 | |
| }, | |
| "tool": { | |
| "type": "string", | |
| "sanitize": true, | |
| "maxLength": 128 | |
| } | |
| } | |
| }, | |
| "noop": { | |
| "defaultMax": 1, | |
| "fields": { | |
| "message": { | |
| "required": true, | |
| "type": "string", | |
| "sanitize": true, | |
| "maxLength": 65000 | |
| } | |
| } | |
| }, | |
| "report_incomplete": { | |
| "defaultMax": 5, | |
| "fields": { | |
| "details": { | |
| "type": "string", | |
| "sanitize": true, | |
| "maxLength": 65000 | |
| }, | |
| "reason": { | |
| "required": true, | |
| "type": "string", | |
| "sanitize": true, | |
| "maxLength": 1024 | |
| } | |
| } | |
| } | |
| } | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| with: | |
| script: | | |
| const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); | |
| setupGlobals(core, github, context, exec, io, getOctokit); | |
| const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs'); | |
| await main(); | |
| - name: Generate Safe Outputs MCP Server Config | |
| id: safe-outputs-config | |
| run: | | |
| # Generate a secure random API key (360 bits of entropy, 40+ chars) | |
| # Mask immediately to prevent timing vulnerabilities | |
| API_KEY=$(openssl rand -base64 45 | tr -d '/+=') | |
| echo "::add-mask::${API_KEY}" | |
| PORT=3001 | |
| # Set outputs for next steps | |
| { | |
| echo "safe_outputs_api_key=${API_KEY}" | |
| echo "safe_outputs_port=${PORT}" | |
| } >> "$GITHUB_OUTPUT" | |
| echo "Safe Outputs MCP server will run on port ${PORT}" | |
| - name: Start Safe Outputs MCP HTTP Server | |
| id: safe-outputs-start | |
| env: | |
| DEBUG: '*' | |
| GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} | |
| GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} | |
| GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} | |
| GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json | |
| GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/config.json | |
| GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs | |
| run: | | |
| # Environment variables are set above to prevent template injection | |
| export DEBUG | |
| export GH_AW_SAFE_OUTPUTS | |
| export GH_AW_SAFE_OUTPUTS_PORT | |
| export GH_AW_SAFE_OUTPUTS_API_KEY | |
| export GH_AW_SAFE_OUTPUTS_TOOLS_PATH | |
| export GH_AW_SAFE_OUTPUTS_CONFIG_PATH | |
| export GH_AW_MCP_LOG_DIR | |
| bash "${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh" | |
| - name: Start MCP Gateway | |
| id: start-mcp-gateway | |
| env: | |
| GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} | |
| GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} | |
| GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} | |
| GITHUB_MCP_SERVER_TOKEN: ${{ steps.github-mcp-app-token.outputs.token }} | |
| run: | | |
| set -eo pipefail | |
| mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config" | |
| # Export gateway environment variables for MCP config and gateway script | |
| export MCP_GATEWAY_PORT="8080" | |
| export MCP_GATEWAY_DOMAIN="host.docker.internal" | |
| export MCP_GATEWAY_HOST_DOMAIN="localhost" | |
| MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=') | |
| echo "::add-mask::${MCP_GATEWAY_API_KEY}" | |
| export MCP_GATEWAY_API_KEY | |
| export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads" | |
| mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}" | |
| export MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD="524288" | |
| export DEBUG="*" | |
| export GH_AW_ENGINE="copilot" | |
| MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') | |
| MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') | |
| DOCKER_SOCK_GID=$(stat -c '%g' /var/run/docker.sock 2>/dev/null || echo '0') | |
| export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.6' | |
| mkdir -p /home/runner/.copilot | |
| GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) | |
| cat << GH_AW_MCP_CONFIG_82c3ebce7c1f546e_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" | |
| { | |
| "mcpServers": { | |
| "github": { | |
| "type": "stdio", | |
| "container": "ghcr.io/github/github-mcp-server:v1.0.3", | |
| "env": { | |
| "GITHUB_HOST": "\${GITHUB_SERVER_URL}", | |
| "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", | |
| "GITHUB_READ_ONLY": "1", | |
| "GITHUB_TOOLSETS": "repos,issues,pull_requests" | |
| }, | |
| "guard-policies": { | |
| "allow-only": { | |
| "approval-labels": ${{ steps.parse-guard-vars.outputs.approval_labels }}, | |
| "blocked-users": ${{ steps.parse-guard-vars.outputs.blocked_users }}, | |
| "min-integrity": "approved", | |
| "repos": [ | |
| "microsoft/*" | |
| ], | |
| "trusted-users": ${{ steps.parse-guard-vars.outputs.trusted_users }} | |
| } | |
| } | |
| }, | |
| "safeoutputs": { | |
| "type": "http", | |
| "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT", | |
| "headers": { | |
| "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" | |
| }, | |
| "guard-policies": { | |
| "write-sink": { | |
| "accept": [ | |
| "private:microsoft" | |
| ] | |
| } | |
| } | |
| } | |
| }, | |
| "gateway": { | |
| "port": $MCP_GATEWAY_PORT, | |
| "domain": "${MCP_GATEWAY_DOMAIN}", | |
| "apiKey": "${MCP_GATEWAY_API_KEY}", | |
| "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" | |
| } | |
| } | |
| GH_AW_MCP_CONFIG_82c3ebce7c1f546e_EOF | |
| - name: Mount MCP servers as CLIs | |
| id: mount-mcp-clis | |
| continue-on-error: true | |
| env: | |
| MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} | |
| MCP_GATEWAY_DOMAIN: ${{ steps.start-mcp-gateway.outputs.gateway-domain }} | |
| MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| with: | |
| script: | | |
| const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); | |
| setupGlobals(core, github, context, exec, io); | |
| const { main } = require('${{ runner.temp }}/gh-aw/actions/mount_mcp_as_cli.cjs'); | |
| await main(); | |
| - name: Clean credentials | |
| continue-on-error: true | |
| run: bash "${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh" | |
| - name: Audit pre-agent workspace | |
| id: pre_agent_audit | |
| continue-on-error: true | |
| run: bash "${RUNNER_TEMP}/gh-aw/actions/audit_pre_agent_workspace.sh" | |
| - name: Execute GitHub Copilot CLI | |
| id: agentic_execution | |
| # Copilot CLI tool arguments (sorted): | |
| timeout-minutes: 20 | |
| run: | | |
| set -o pipefail | |
| touch /tmp/gh-aw/agent-step-summary.md | |
| GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) | |
| export GH_AW_NODE_BIN | |
| (umask 177 && touch /tmp/gh-aw/agent-stdio.log) | |
| printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.41/awf-config.schema.json","network":{"allowDomains":["*.githubusercontent.com","api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","api.snapcraft.io","archive.ubuntu.com","azure.archive.ubuntu.com","codeload.github.com","crl.geotrust.com","crl.globalsign.com","crl.identrust.com","crl.sectigo.com","crl.thawte.com","crl.usertrust.com","crl.verisign.com","crl3.digicert.com","crl4.digicert.com","crls.ssl.com","docs.github.com","github-cloud.githubusercontent.com","github-cloud.s3.amazonaws.com","github.blog","github.com","github.githubassets.com","host.docker.internal","json-schema.org","json.schemastore.org","keyserver.ubuntu.com","lfs.github.com","objects.githubusercontent.com","ocsp.digicert.com","ocsp.geotrust.com","ocsp.globalsign.com","ocsp.identrust.com","ocsp.sectigo.com","ocsp.ssl.com","ocsp.thawte.com","ocsp.usertrust.com","ocsp.verisign.com","packagecloud.io","packages.cloud.google.com","packages.microsoft.com","ppa.launchpad.net","raw.githubusercontent.com","registry.npmjs.org","s.symcb.com","s.symcd.com","security.ubuntu.com","telemetry.enterprise.githubcopilot.com","ts-crl.ws.symantec.com","ts-ocsp.ws.symantec.com","www.googleapis.com"]},"apiProxy":{"enabled":true,"models":{"auto":["large"],"deep-research":["copilot/deep-research*","google/deep-research*"],"gemini-flash":["copilot/gemini-*flash*","google/gemini-*flash*"],"gemini-pro":["copilot/gemini-*pro*","google/gemini-*pro*"],"gpt-4.1":["copilot/gpt-4.1*","openai/gpt-4.1*"],"gpt-5":["copilot/gpt-5*","openai/gpt-5*"],"gpt-5-codex":["copilot/gpt-5*codex*","openai/gpt-5*codex*"],"gpt-5-mini":["copilot/gpt-5*mini*","openai/gpt-5*mini*"],"gpt-5-nano":["copilot/gpt-5*nano*","openai/gpt-5*nano*"],"gpt-5-pro":["copilot/gpt-5*pro*","openai/gpt-5*pro*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash"],"opus":["copilot/*opus*","anthropic/*opus*"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"small":["mini"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"]}},"container":{"imageTag":"0.25.41"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" && cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json | |
| # shellcheck disable=SC1003 | |
| sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ | |
| -- /bin/bash -c 'export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log | |
| env: | |
| COPILOT_AGENT_RUNNER_TYPE: STANDALONE | |
| COPILOT_API_KEY: dummy-byok-key-for-offline-mode | |
| COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} | |
| COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'claude-sonnet-4.6' }} | |
| GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json | |
| GH_AW_PHASE: agent | |
| GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt | |
| GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} | |
| GH_AW_VERSION: v0.72.0 | |
| GITHUB_API_URL: ${{ github.api_url }} | |
| GITHUB_AW: true | |
| GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows | |
| GITHUB_HEAD_REF: ${{ github.head_ref }} | |
| GITHUB_MCP_SERVER_TOKEN: ${{ steps.github-mcp-app-token.outputs.token }} | |
| GITHUB_REF_NAME: ${{ github.ref_name }} | |
| GITHUB_SERVER_URL: ${{ github.server_url }} | |
| GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md | |
| GITHUB_WORKSPACE: ${{ github.workspace }} | |
| GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com | |
| GIT_AUTHOR_NAME: github-actions[bot] | |
| GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com | |
| GIT_COMMITTER_NAME: github-actions[bot] | |
| XDG_CONFIG_HOME: /home/runner | |
| - name: Detect Copilot errors | |
| id: detect-copilot-errors | |
| if: always() | |
| continue-on-error: true | |
| run: node "${RUNNER_TEMP}/gh-aw/actions/detect_copilot_errors.cjs" | |
| - name: Configure Git credentials | |
| env: | |
| REPO_NAME: ${{ github.repository }} | |
| SERVER_URL: ${{ github.server_url }} | |
| GITHUB_TOKEN: ${{ github.token }} | |
| run: | | |
| git config --global user.email "github-actions[bot]@users.noreply.github.com" | |
| git config --global user.name "github-actions[bot]" | |
| git config --global am.keepcr true | |
| # Re-authenticate git with GitHub token | |
| SERVER_URL_STRIPPED="${SERVER_URL#https://}" | |
| git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" | |
| echo "Git configured with standard GitHub Actions identity" | |
| - name: Copy Copilot session state files to logs | |
| if: always() | |
| continue-on-error: true | |
| run: bash "${RUNNER_TEMP}/gh-aw/actions/copy_copilot_session_state.sh" | |
| - name: Stop MCP Gateway | |
| if: always() | |
| continue-on-error: true | |
| env: | |
| MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} | |
| MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} | |
| GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }} | |
| run: | | |
| bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID" | |
| - name: Redact secrets in logs | |
| if: always() | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| with: | |
| script: | | |
| const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); | |
| setupGlobals(core, github, context, exec, io, getOctokit); | |
| const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs'); | |
| await main(); | |
| env: | |
| GH_AW_SECRET_NAMES: 'ASPIRE_BOT_APP_ID,ASPIRE_BOT_PRIVATE_KEY,COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' | |
| SECRET_ASPIRE_BOT_APP_ID: ${{ secrets.ASPIRE_BOT_APP_ID }} | |
| SECRET_ASPIRE_BOT_PRIVATE_KEY: ${{ secrets.ASPIRE_BOT_PRIVATE_KEY }} | |
| SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} | |
| SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} | |
| SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} | |
| SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Append agent step summary | |
| if: always() | |
| run: bash "${RUNNER_TEMP}/gh-aw/actions/append_agent_step_summary.sh" | |
| - name: Copy Safe Outputs | |
| if: always() | |
| env: | |
| GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} | |
| run: | | |
| mkdir -p /tmp/gh-aw | |
| cp "$GH_AW_SAFE_OUTPUTS" /tmp/gh-aw/safeoutputs.jsonl 2>/dev/null || true | |
| - name: Ingest agent output | |
| id: collect_output | |
| if: always() | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} | |
| GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,docs.github.com,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" | |
| GITHUB_SERVER_URL: ${{ github.server_url }} | |
| GITHUB_API_URL: ${{ github.api_url }} | |
| with: | |
| script: | | |
| const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); | |
| setupGlobals(core, github, context, exec, io, getOctokit); | |
| const { main } = require('${{ runner.temp }}/gh-aw/actions/collect_ndjson_output.cjs'); | |
| await main(); | |
| - name: Parse agent logs for step summary | |
| if: always() | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/ | |
| with: | |
| script: | | |
| const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); | |
| setupGlobals(core, github, context, exec, io, getOctokit); | |
| const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_copilot_log.cjs'); | |
| await main(); | |
| - name: Parse MCP Gateway logs for step summary | |
| if: always() | |
| id: parse-mcp-gateway | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| with: | |
| script: | | |
| const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); | |
| setupGlobals(core, github, context, exec, io, getOctokit); | |
| const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_mcp_gateway_log.cjs'); | |
| await main(); | |
| - name: Print firewall logs | |
| if: always() | |
| continue-on-error: true | |
| env: | |
| AWF_LOGS_DIR: /tmp/gh-aw/sandbox/firewall/logs | |
| run: | | |
| # Fix permissions on firewall logs/audit dirs so they can be uploaded as artifacts | |
| # AWF runs with sudo, creating files owned by root | |
| sudo chmod -R a+rX /tmp/gh-aw/sandbox/firewall 2>/dev/null || true | |
| # Only run awf logs summary if awf command exists (it may not be installed if workflow failed before install step) | |
| if command -v awf &> /dev/null; then | |
| awf logs summary | tee -a "$GITHUB_STEP_SUMMARY" | |
| else | |
| echo 'AWF binary not installed, skipping firewall log summary' | |
| fi | |
| - name: Parse token usage for step summary | |
| if: always() | |
| continue-on-error: true | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| with: | |
| script: | | |
| const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); | |
| setupGlobals(core, github, context, exec, io, getOctokit); | |
| const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs'); | |
| await main(); | |
| - name: Print AWF reflect summary | |
| if: always() | |
| continue-on-error: true | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| with: | |
| script: | | |
| const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); | |
| setupGlobals(core, github, context, exec, io, getOctokit); | |
| const { main } = require('${{ runner.temp }}/gh-aw/actions/awf_reflect_summary.cjs'); | |
| await main(); | |
| - name: Write agent output placeholder if missing | |
| if: always() | |
| run: | | |
| if [ ! -f /tmp/gh-aw/agent_output.json ]; then | |
| echo '{"items":[]}' > /tmp/gh-aw/agent_output.json | |
| fi | |
| - name: Upload agent artifacts | |
| if: always() | |
| continue-on-error: true | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: agent | |
| path: | | |
| /tmp/gh-aw/aw-prompts/prompt.txt | |
| /tmp/gh-aw/sandbox/agent/logs/ | |
| /tmp/gh-aw/redacted-urls.log | |
| /tmp/gh-aw/mcp-logs/ | |
| /tmp/gh-aw/proxy-logs/ | |
| !/tmp/gh-aw/proxy-logs/proxy-tls/ | |
| /tmp/gh-aw/agent_usage.json | |
| /tmp/gh-aw/agent-stdio.log | |
| /tmp/gh-aw/pre-agent-audit.txt | |
| /tmp/gh-aw/agent/ | |
| /tmp/gh-aw/github_rate_limits.jsonl | |
| /tmp/gh-aw/safeoutputs.jsonl | |
| /tmp/gh-aw/agent_output.json | |
| /tmp/gh-aw/aw-*.patch | |
| /tmp/gh-aw/aw-*.bundle | |
| /tmp/gh-aw/awf-config.json | |
| /tmp/gh-aw/sandbox/firewall/logs/ | |
| /tmp/gh-aw/sandbox/firewall/audit/ | |
| /tmp/gh-aw/sandbox/firewall/awf-reflect.json | |
| if-no-files-found: ignore | |
| - name: Invalidate GitHub App token | |
| if: always() && steps.github-mcp-app-token.outputs.token != '' | |
| env: | |
| TOKEN: ${{ steps.github-mcp-app-token.outputs.token }} | |
| run: | | |
| echo "Revoking GitHub App installation token..." | |
| # GitHub CLI will auth with the token being revoked. | |
| gh api \ | |
| --method DELETE \ | |
| -H "Authorization: token $TOKEN" \ | |
| /installation/token || echo "Token revoke may already be expired." | |
| echo "Token invalidation step complete." | |
| - name: Invalidate checkout app token (0) | |
| if: always() && steps.checkout-app-token-0.outputs.token != '' | |
| env: | |
| TOKEN: ${{ steps.checkout-app-token-0.outputs.token }} | |
| run: | | |
| echo "Revoking GitHub App installation token..." | |
| # GitHub CLI will auth with the token being revoked. | |
| gh api \ | |
| --method DELETE \ | |
| -H "Authorization: token $TOKEN" \ | |
| /installation/token || echo "Token revoke may already be expired." | |
| echo "Token invalidation step complete." | |
| - name: Invalidate checkout app token (1) | |
| if: always() && steps.checkout-app-token-1.outputs.token != '' | |
| env: | |
| TOKEN: ${{ steps.checkout-app-token-1.outputs.token }} | |
| run: | | |
| echo "Revoking GitHub App installation token..." | |
| # GitHub CLI will auth with the token being revoked. | |
| gh api \ | |
| --method DELETE \ | |
| -H "Authorization: token $TOKEN" \ | |
| /installation/token || echo "Token revoke may already be expired." | |
| echo "Token invalidation step complete." | |
| conclusion: | |
| needs: | |
| - activation | |
| - agent | |
| - detection | |
| - notify_source_pr | |
| - safe_outputs | |
| if: > | |
| always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' || | |
| needs.activation.outputs.stale_lock_file_failed == 'true') | |
| runs-on: ubuntu-slim | |
| permissions: | |
| contents: write | |
| issues: write | |
| pull-requests: write | |
| concurrency: | |
| group: "gh-aw-conclusion-pr-docs-check" | |
| cancel-in-progress: false | |
| outputs: | |
| incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} | |
| noop_message: ${{ steps.noop.outputs.noop_message }} | |
| tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} | |
| total_count: ${{ steps.missing_tool.outputs.total_count }} | |
| steps: | |
| - name: Setup Scripts | |
| id: setup | |
| uses: github/gh-aw-actions/setup@ff0525b685481744f490a0d362753d8001e4b39d # v0.72.0 | |
| with: | |
| destination: ${{ runner.temp }}/gh-aw/actions | |
| job-name: ${{ github.job }} | |
| trace-id: ${{ needs.activation.outputs.setup-trace-id }} | |
| env: | |
| GH_AW_SETUP_WORKFLOW_NAME: "PR Documentation Check" | |
| GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/pr-docs-check.lock.yml@${{ github.ref }} | |
| GH_AW_INFO_VERSION: "1.0.40" | |
| - name: Generate GitHub App token | |
| id: safe-outputs-app-token | |
| uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 | |
| with: | |
| client-id: ${{ secrets.ASPIRE_BOT_APP_ID }} | |
| private-key: ${{ secrets.ASPIRE_BOT_PRIVATE_KEY }} | |
| owner: microsoft | |
| repositories: |- | |
| aspire.dev | |
| aspire | |
| github-api-url: ${{ github.api_url }} | |
| permission-contents: write | |
| permission-issues: write | |
| permission-pull-requests: write | |
| - name: Download agent output artifact | |
| id: download-agent-output | |
| continue-on-error: true | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: agent | |
| path: /tmp/gh-aw/ | |
| - name: Setup agent output environment variable | |
| id: setup-agent-output-env | |
| if: steps.download-agent-output.outcome == 'success' | |
| run: | | |
| mkdir -p /tmp/gh-aw/ | |
| find "/tmp/gh-aw/" -type f -print | |
| echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" | |
| - name: Process no-op messages | |
| id: noop | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} | |
| GH_AW_NOOP_MAX: "1" | |
| GH_AW_WORKFLOW_NAME: "PR Documentation Check" | |
| GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} | |
| GH_AW_NOOP_REPORT_AS_ISSUE: "true" | |
| with: | |
| github-token: ${{ steps.safe-outputs-app-token.outputs.token }} | |
| script: | | |
| const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); | |
| setupGlobals(core, github, context, exec, io, getOctokit); | |
| const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs'); | |
| await main(); | |
| - name: Log detection run | |
| id: detection_runs | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} | |
| GH_AW_WORKFLOW_NAME: "PR Documentation Check" | |
| GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} | |
| GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} | |
| with: | |
| github-token: ${{ steps.safe-outputs-app-token.outputs.token }} | |
| script: | | |
| const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); | |
| setupGlobals(core, github, context, exec, io, getOctokit); | |
| const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_detection_runs.cjs'); | |
| await main(); | |
| - name: Record missing tool | |
| id: missing_tool | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} | |
| GH_AW_MISSING_TOOL_CREATE_ISSUE: "true" | |
| GH_AW_WORKFLOW_NAME: "PR Documentation Check" | |
| with: | |
| github-token: ${{ steps.safe-outputs-app-token.outputs.token }} | |
| script: | | |
| const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); | |
| setupGlobals(core, github, context, exec, io, getOctokit); | |
| const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs'); | |
| await main(); | |
| - name: Record incomplete | |
| id: report_incomplete | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} | |
| GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true" | |
| GH_AW_WORKFLOW_NAME: "PR Documentation Check" | |
| with: | |
| github-token: ${{ steps.safe-outputs-app-token.outputs.token }} | |
| script: | | |
| const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); | |
| setupGlobals(core, github, context, exec, io, getOctokit); | |
| const { main } = require('${{ runner.temp }}/gh-aw/actions/report_incomplete_handler.cjs'); | |
| await main(); | |
| - name: Handle agent failure | |
| id: handle_agent_failure | |
| if: always() | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} | |
| GH_AW_WORKFLOW_NAME: "PR Documentation Check" | |
| GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} | |
| GH_AW_WORKFLOW_ID: "pr-docs-check" | |
| GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168" | |
| GH_AW_ENGINE_ID: "copilot" | |
| GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} | |
| GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} | |
| GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }} | |
| GH_AW_MCP_POLICY_ERROR: ${{ needs.agent.outputs.mcp_policy_error }} | |
| GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }} | |
| GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.agent.outputs.model_not_supported_error }} | |
| GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com" | |
| GH_AW_CODE_PUSH_FAILURE_ERRORS: ${{ needs.safe_outputs.outputs.code_push_failure_errors }} | |
| GH_AW_CODE_PUSH_FAILURE_COUNT: ${{ needs.safe_outputs.outputs.code_push_failure_count }} | |
| GH_AW_SAFE_OUTPUTS_APP_TOKEN_MINTING_FAILED: ${{ needs.safe_outputs.outputs.app_token_minting_failed }} | |
| GH_AW_CONCLUSION_APP_TOKEN_MINTING_FAILED: ${{ steps.safe-outputs-app-token.outcome == 'failure' }} | |
| GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} | |
| GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }} | |
| GH_AW_GROUP_REPORTS: "false" | |
| GH_AW_FAILURE_REPORT_AS_ISSUE: "true" | |
| GH_AW_MISSING_TOOL_REPORT_AS_FAILURE: "true" | |
| GH_AW_MISSING_DATA_REPORT_AS_FAILURE: "true" | |
| GH_AW_TIMEOUT_MINUTES: "20" | |
| with: | |
| github-token: ${{ steps.safe-outputs-app-token.outputs.token }} | |
| script: | | |
| const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); | |
| setupGlobals(core, github, context, exec, io, getOctokit); | |
| const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs'); | |
| await main(); | |
| - name: Invalidate GitHub App token | |
| if: always() && steps.safe-outputs-app-token.outputs.token != '' | |
| env: | |
| TOKEN: ${{ steps.safe-outputs-app-token.outputs.token }} | |
| run: | | |
| echo "Revoking GitHub App installation token..." | |
| # GitHub CLI will auth with the token being revoked. | |
| gh api \ | |
| --method DELETE \ | |
| -H "Authorization: token $TOKEN" \ | |
| /installation/token || echo "Token revoke may already be expired." | |
| echo "Token invalidation step complete." | |
| detection: | |
| needs: | |
| - activation | |
| - agent | |
| if: > | |
| always() && needs.agent.result != 'skipped' && (needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true') | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| outputs: | |
| detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} | |
| detection_reason: ${{ steps.detection_conclusion.outputs.reason }} | |
| detection_success: ${{ steps.detection_conclusion.outputs.success }} | |
| steps: | |
| - name: Setup Scripts | |
| id: setup | |
| uses: github/gh-aw-actions/setup@ff0525b685481744f490a0d362753d8001e4b39d # v0.72.0 | |
| with: | |
| destination: ${{ runner.temp }}/gh-aw/actions | |
| job-name: ${{ github.job }} | |
| trace-id: ${{ needs.activation.outputs.setup-trace-id }} | |
| env: | |
| GH_AW_SETUP_WORKFLOW_NAME: "PR Documentation Check" | |
| GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/pr-docs-check.lock.yml@${{ github.ref }} | |
| GH_AW_INFO_VERSION: "1.0.40" | |
| - name: Download agent output artifact | |
| id: download-agent-output | |
| continue-on-error: true | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: agent | |
| path: /tmp/gh-aw/ | |
| - name: Setup agent output environment variable | |
| id: setup-agent-output-env | |
| if: steps.download-agent-output.outcome == 'success' | |
| run: | | |
| mkdir -p /tmp/gh-aw/ | |
| find "/tmp/gh-aw/" -type f -print | |
| echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" | |
| - name: Checkout repository for patch context | |
| if: needs.agent.outputs.has_patch == 'true' | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| # --- Threat Detection --- | |
| - name: Clean stale firewall files from agent artifact | |
| run: | | |
| rm -rf /tmp/gh-aw/sandbox/firewall/logs | |
| rm -rf /tmp/gh-aw/sandbox/firewall/audit | |
| - name: Download container images | |
| run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.41 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41 ghcr.io/github/gh-aw-firewall/squid:0.25.41 | |
| - name: Check if detection needed | |
| id: detection_guard | |
| if: always() | |
| env: | |
| OUTPUT_TYPES: ${{ needs.agent.outputs.output_types }} | |
| HAS_PATCH: ${{ needs.agent.outputs.has_patch }} | |
| run: | | |
| if [[ -n "$OUTPUT_TYPES" || "$HAS_PATCH" == "true" ]]; then | |
| echo "run_detection=true" >> "$GITHUB_OUTPUT" | |
| echo "Detection will run: output_types=$OUTPUT_TYPES, has_patch=$HAS_PATCH" | |
| else | |
| echo "run_detection=false" >> "$GITHUB_OUTPUT" | |
| echo "Detection skipped: no agent outputs or patches to analyze" | |
| fi | |
| - name: Clear MCP Config for detection | |
| if: always() && steps.detection_guard.outputs.run_detection == 'true' | |
| run: | | |
| rm -f "${RUNNER_TEMP}/gh-aw/mcp-config/mcp-servers.json" | |
| rm -f /home/runner/.copilot/mcp-config.json | |
| rm -f "$GITHUB_WORKSPACE/.gemini/settings.json" | |
| - name: Prepare threat detection files | |
| if: always() && steps.detection_guard.outputs.run_detection == 'true' | |
| run: | | |
| mkdir -p /tmp/gh-aw/threat-detection/aw-prompts | |
| cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true | |
| cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true | |
| for f in /tmp/gh-aw/aw-*.patch; do | |
| [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true | |
| done | |
| for f in /tmp/gh-aw/aw-*.bundle; do | |
| [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true | |
| done | |
| echo "Prepared threat detection files:" | |
| ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true | |
| - name: Setup threat detection | |
| if: always() && steps.detection_guard.outputs.run_detection == 'true' | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| WORKFLOW_NAME: "PR Documentation Check" | |
| WORKFLOW_DESCRIPTION: "Analyzes merged pull requests for significant user-facing changes. When a\nPR is merged against main or release/* branches, this workflow determines\nwhether microsoft/aspire.dev needs a documentation PR. If documentation\nupdates are required, it creates a draft PR with the changes following the\ndoc-writer skill conventions. The draft PR targets the aspire.dev branch\nresolved from the source PR's release reasoning (PR milestone, linked-issue\nmilestone, then source PR base), using the matching release/* branch when it\nalready exists and falling back to aspire.dev main otherwise. It also\ncomments on the original PR with a link to the draft PR (or a \"no docs\nneeded\" message)." | |
| HAS_PATCH: ${{ needs.agent.outputs.has_patch }} | |
| with: | |
| script: | | |
| const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); | |
| setupGlobals(core, github, context, exec, io, getOctokit); | |
| const { main } = require('${{ runner.temp }}/gh-aw/actions/setup_threat_detection.cjs'); | |
| await main(); | |
| - name: Ensure threat-detection directory and log | |
| if: always() && steps.detection_guard.outputs.run_detection == 'true' | |
| run: | | |
| mkdir -p /tmp/gh-aw/threat-detection | |
| touch /tmp/gh-aw/threat-detection/detection.log | |
| - name: Setup Node.js | |
| uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| with: | |
| node-version: '24' | |
| package-manager-cache: false | |
| - name: Install GitHub Copilot CLI | |
| run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.40 | |
| env: | |
| GH_HOST: github.com | |
| - name: Install AWF binary | |
| run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.41 | |
| - name: Execute GitHub Copilot CLI | |
| if: always() && steps.detection_guard.outputs.run_detection == 'true' | |
| continue-on-error: true | |
| id: detection_agentic_execution | |
| # Copilot CLI tool arguments (sorted): | |
| timeout-minutes: 20 | |
| run: | | |
| set -o pipefail | |
| touch /tmp/gh-aw/agent-step-summary.md | |
| GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) | |
| export GH_AW_NODE_BIN | |
| (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) | |
| printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.41/awf-config.schema.json","network":{"allowDomains":["api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","github.com","host.docker.internal","telemetry.enterprise.githubcopilot.com"]},"apiProxy":{"enabled":true},"container":{"imageTag":"0.25.41"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" && cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json | |
| # shellcheck disable=SC1003 | |
| sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ | |
| -- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log | |
| env: | |
| COPILOT_AGENT_RUNNER_TYPE: STANDALONE | |
| COPILOT_API_KEY: dummy-byok-key-for-offline-mode | |
| COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} | |
| COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || 'claude-sonnet-4.6' }} | |
| GH_AW_PHASE: detection | |
| GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt | |
| GH_AW_VERSION: v0.72.0 | |
| GITHUB_API_URL: ${{ github.api_url }} | |
| GITHUB_AW: true | |
| GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows | |
| GITHUB_HEAD_REF: ${{ github.head_ref }} | |
| GITHUB_REF_NAME: ${{ github.ref_name }} | |
| GITHUB_SERVER_URL: ${{ github.server_url }} | |
| GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md | |
| GITHUB_WORKSPACE: ${{ github.workspace }} | |
| GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com | |
| GIT_AUTHOR_NAME: github-actions[bot] | |
| GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com | |
| GIT_COMMITTER_NAME: github-actions[bot] | |
| XDG_CONFIG_HOME: /home/runner | |
| - name: Upload threat detection log | |
| if: always() && steps.detection_guard.outputs.run_detection == 'true' | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: detection | |
| path: /tmp/gh-aw/threat-detection/detection.log | |
| if-no-files-found: ignore | |
| - name: Parse and conclude threat detection | |
| id: detection_conclusion | |
| if: always() | |
| continue-on-error: true | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }} | |
| GH_AW_DETECTION_CONTINUE_ON_ERROR: "true" | |
| with: | |
| script: | | |
| try { | |
| const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); | |
| setupGlobals(core, github, context, exec, io, getOctokit); | |
| const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs'); | |
| await main(); | |
| } catch (loadErr) { | |
| const continueOnError = process.env.GH_AW_DETECTION_CONTINUE_ON_ERROR !== 'false'; | |
| const msg = 'ERR_SYSTEM: \u274C Unexpected error loading threat detection module: ' + (loadErr && loadErr.message ? loadErr.message : String(loadErr)); | |
| core.error(msg); | |
| core.setOutput('reason', 'parse_error'); | |
| if (continueOnError) { | |
| core.warning('\u26A0\uFE0F ' + msg); | |
| core.setOutput('conclusion', 'warning'); | |
| core.setOutput('success', 'false'); | |
| } else { | |
| core.setOutput('conclusion', 'failure'); | |
| core.setOutput('success', 'false'); | |
| core.setFailed(msg); | |
| } | |
| } | |
| notify_source_pr: | |
| name: Notify source PR | |
| needs: | |
| - agent | |
| - detection | |
| - safe_outputs | |
| if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'notify_source_pr') | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Download agent output artifact | |
| continue-on-error: true | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: agent | |
| path: ${{ runner.temp }}/gh-aw/safe-jobs/ | |
| - name: Configure Safe Outputs Job Environment Variables | |
| id: setup-safe-job-env | |
| run: | | |
| find "${RUNNER_TEMP}/gh-aw/safe-jobs/" -type f -print | |
| echo "GH_AW_AGENT_OUTPUT=${RUNNER_TEMP}/gh-aw/safe-jobs/agent_output.json" >> "$GITHUB_OUTPUT" | |
| - name: Mint aspire-bot token (microsoft/aspire) | |
| id: aspire-token | |
| uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 | |
| env: | |
| GH_AW_AGENT_OUTPUT: ${{ steps.setup-safe-job-env.outputs.GH_AW_AGENT_OUTPUT }} | |
| with: | |
| app-id: ${{ secrets.ASPIRE_BOT_APP_ID }} | |
| owner: microsoft | |
| private-key: ${{ secrets.ASPIRE_BOT_PRIVATE_KEY }} | |
| repositories: aspire | |
| - name: Mint aspire-bot token (microsoft/aspire.dev) | |
| id: aspire-dev-token | |
| if: needs.safe_outputs.outputs.created_pr_url != '' | |
| uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 | |
| env: | |
| GH_AW_AGENT_OUTPUT: ${{ steps.setup-safe-job-env.outputs.GH_AW_AGENT_OUTPUT }} | |
| with: | |
| app-id: ${{ secrets.ASPIRE_BOT_APP_ID }} | |
| owner: microsoft | |
| private-key: ${{ secrets.ASPIRE_BOT_PRIVATE_KEY }} | |
| repositories: aspire.dev | |
| - name: Post status comment on source PR | |
| uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 | |
| env: | |
| DRAFT_PR_NUMBER: ${{ needs.safe_outputs.outputs.created_pr_number }} | |
| DRAFT_PR_URL: ${{ needs.safe_outputs.outputs.created_pr_url }} | |
| GH_AW_AGENT_OUTPUT: ${{ steps.setup-safe-job-env.outputs.GH_AW_AGENT_OUTPUT }} | |
| with: | |
| github-token: ${{ steps.aspire-token.outputs.token }} | |
| script: | | |
| const fs = require('fs'); | |
| const MARKER = '<!-- pr-docs-check:notify-source-pr -->'; | |
| const SUMMARY_MAX = 2000; | |
| const outputPath = process.env.GH_AW_AGENT_OUTPUT; | |
| if (!outputPath || !fs.existsSync(outputPath)) { | |
| core.warning(`Agent output file not found at ${outputPath}; skipping comment.`); | |
| return; | |
| } | |
| let payload; | |
| try { | |
| payload = JSON.parse(fs.readFileSync(outputPath, 'utf8')); | |
| } catch (e) { | |
| core.warning(`Failed to parse agent output: ${e.message}`); | |
| return; | |
| } | |
| const items = (payload && Array.isArray(payload.items)) ? payload.items : []; | |
| const item = items.find(i => i && i.type === 'notify_source_pr'); | |
| if (!item) { | |
| core.info('No notify_source_pr item in agent output; nothing to post.'); | |
| return; | |
| } | |
| // Source PR number is supplied by the agent. Validate it as a | |
| // positive integer with a sane upper bound; the safe-jobs framework | |
| // does not pass workflow-context expressions through env: cleanly, | |
| // and threat detection has already gated this output. | |
| const agentNumber = parseInt(String(item.source_pr_number), 10); | |
| if (!Number.isInteger(agentNumber) || agentNumber <= 0 || agentNumber > 10_000_000) { | |
| core.warning(`Invalid source_pr_number from agent: ${item.source_pr_number}; skipping comment.`); | |
| return; | |
| } | |
| const sourcePrNumber = agentNumber; | |
| const result = (item.result || '').toString().trim().toLowerCase(); | |
| const targetBranch = (item.target_branch || '').toString().trim(); | |
| const draftUrl = (process.env.DRAFT_PR_URL || '').trim(); | |
| const draftNumber = (process.env.DRAFT_PR_NUMBER || '').trim(); | |
| // Bound the agent-supplied summary so a malformed item can't blow up the comment. | |
| let summary = (item.summary || '').toString().trim(); | |
| if (summary.length > SUMMARY_MAX) { | |
| summary = summary.slice(0, SUMMARY_MAX) + '\n\n_(summary truncated)_'; | |
| } | |
| let body; | |
| if (result === 'drafted' && draftUrl) { | |
| const branchSuffix = targetBranch ? ` targeting \`${targetBranch}\`` : ''; | |
| const numberDisplay = draftNumber || '?'; | |
| body = [ | |
| MARKER, | |
| `📝 Documentation has been drafted in [microsoft/aspire.dev#${numberDisplay}](${draftUrl})${branchSuffix}.`, | |
| '', | |
| summary, | |
| '', | |
| '> [!NOTE]', | |
| '> This draft PR needs human review before merging.' | |
| ].join('\n'); | |
| } else if (result === 'drafted') { | |
| // Agent intended to draft a PR but the safe-outputs handler did not produce | |
| // a created_pr_url. Surface this as a failure rather than a "skipped" result. | |
| body = [ | |
| MARKER, | |
| '⚠️ Documentation drafting was attempted but the draft PR could not be confirmed.', | |
| '', | |
| `See the workflow run for details: ${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`, | |
| '', | |
| summary | |
| ].join('\n'); | |
| } else { | |
| body = [ | |
| MARKER, | |
| '✅ No documentation update needed.', | |
| '', | |
| summary | |
| ].join('\n'); | |
| } | |
| // Best-effort hide-older-comments: minimize prior comments on this PR that | |
| // carry our marker. Mirrors the framework's hide-older-comments behavior | |
| // for re-runs of the same workflow on the same PR. | |
| try { | |
| const existing = await github.paginate(github.rest.issues.listComments, { | |
| owner: 'microsoft', | |
| repo: 'aspire', | |
| issue_number: sourcePrNumber, | |
| per_page: 100, | |
| }); | |
| for (const c of existing) { | |
| if (c.body && c.body.includes(MARKER)) { | |
| try { | |
| await github.graphql( | |
| `mutation($id: ID!) { minimizeComment(input: { subjectId: $id, classifier: OUTDATED }) { minimizedComment { isMinimized } } }`, | |
| { id: c.node_id } | |
| ); | |
| } catch (e) { | |
| core.warning(`Failed to minimize comment ${c.id}: ${e.message}`); | |
| } | |
| } | |
| } | |
| } catch (e) { | |
| core.warning(`Failed to enumerate prior comments: ${e.message}`); | |
| } | |
| await github.rest.issues.createComment({ | |
| owner: 'microsoft', | |
| repo: 'aspire', | |
| issue_number: sourcePrNumber, | |
| body, | |
| }); | |
| core.info(`Posted ${result || 'unknown'} comment on microsoft/aspire#${sourcePrNumber}`); | |
| - name: Request SME review on draft PR | |
| if: needs.safe_outputs.outputs.created_pr_url != '' | |
| uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 | |
| env: | |
| DRAFT_PR_NUMBER: ${{ needs.safe_outputs.outputs.created_pr_number }} | |
| GH_AW_AGENT_OUTPUT: ${{ steps.setup-safe-job-env.outputs.GH_AW_AGENT_OUTPUT }} | |
| with: | |
| github-token: ${{ steps.aspire-dev-token.outputs.token }} | |
| script: | | |
| const fs = require('fs'); | |
| const outputPath = process.env.GH_AW_AGENT_OUTPUT; | |
| if (!outputPath || !fs.existsSync(outputPath)) { | |
| core.info('Agent output file not found; skipping reviewer request.'); | |
| return; | |
| } | |
| let payload; | |
| try { | |
| payload = JSON.parse(fs.readFileSync(outputPath, 'utf8')); | |
| } catch (e) { | |
| core.warning(`Failed to parse agent output: ${e.message}`); | |
| return; | |
| } | |
| const items = (payload && Array.isArray(payload.items)) ? payload.items : []; | |
| const item = items.find(i => i && i.type === 'notify_source_pr'); | |
| if (!item) { | |
| core.info('No notify_source_pr item; skipping reviewer request.'); | |
| return; | |
| } | |
| const sme = (item.sme_login || '').toString().trim().replace(/^@/, ''); | |
| if (!sme) { | |
| core.info('No SME login provided; leaving draft PR without an explicit reviewer.'); | |
| return; | |
| } | |
| const draftNumber = parseInt(String(process.env.DRAFT_PR_NUMBER || ''), 10); | |
| if (!Number.isInteger(draftNumber) || draftNumber <= 0) { | |
| core.warning(`Invalid draft PR number: ${process.env.DRAFT_PR_NUMBER}`); | |
| return; | |
| } | |
| try { | |
| await github.rest.pulls.requestReviewers({ | |
| owner: 'microsoft', | |
| repo: 'aspire.dev', | |
| pull_number: draftNumber, | |
| reviewers: [sme], | |
| }); | |
| core.info(`Requested @${sme} as reviewer on microsoft/aspire.dev#${draftNumber}`); | |
| } catch (e) { | |
| // Best-effort: an SME may not be assignable on aspire.dev (no write access, | |
| // outside collaborator, etc.). Don't fail the job over this. | |
| core.warning(`Failed to request reviewer @${sme} on microsoft/aspire.dev#${draftNumber}: ${e.message}`); | |
| } | |
| pre_activation: | |
| if: > | |
| ((github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch') && github.repository_owner == 'microsoft') && | |
| (github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) | |
| runs-on: ubuntu-slim | |
| outputs: | |
| activated: ${{ steps.check_membership.outputs.is_team_member == 'true' }} | |
| matched_command: '' | |
| setup-trace-id: ${{ steps.setup.outputs.trace-id }} | |
| steps: | |
| - name: Setup Scripts | |
| id: setup | |
| uses: github/gh-aw-actions/setup@ff0525b685481744f490a0d362753d8001e4b39d # v0.72.0 | |
| with: | |
| destination: ${{ runner.temp }}/gh-aw/actions | |
| job-name: ${{ github.job }} | |
| env: | |
| GH_AW_SETUP_WORKFLOW_NAME: "PR Documentation Check" | |
| GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/pr-docs-check.lock.yml@${{ github.ref }} | |
| GH_AW_INFO_VERSION: "1.0.40" | |
| - name: Check team membership for workflow | |
| id: check_membership | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| GH_AW_REQUIRED_ROLES: "admin,maintainer,write" | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); | |
| setupGlobals(core, github, context, exec, io, getOctokit); | |
| const { main } = require('${{ runner.temp }}/gh-aw/actions/check_membership.cjs'); | |
| await main(); | |
| safe_outputs: | |
| needs: | |
| - activation | |
| - agent | |
| - detection | |
| if: (!cancelled()) && needs.agent.result != 'skipped' && needs.detection.result == 'success' | |
| runs-on: ubuntu-slim | |
| permissions: | |
| contents: write | |
| issues: write | |
| pull-requests: write | |
| timeout-minutes: 15 | |
| env: | |
| GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/pr-docs-check" | |
| GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} | |
| GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} | |
| GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} | |
| GH_AW_ENGINE_ID: "copilot" | |
| GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} | |
| GH_AW_ENGINE_VERSION: "1.0.40" | |
| GH_AW_WORKFLOW_ID: "pr-docs-check" | |
| GH_AW_WORKFLOW_NAME: "PR Documentation Check" | |
| outputs: | |
| app_token_minting_failed: ${{ steps.safe-outputs-app-token.outcome == 'failure' }} | |
| code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }} | |
| code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} | |
| create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }} | |
| create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }} | |
| created_pr_number: ${{ steps.process_safe_outputs.outputs.created_pr_number }} | |
| created_pr_url: ${{ steps.process_safe_outputs.outputs.created_pr_url }} | |
| process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }} | |
| process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} | |
| steps: | |
| - name: Setup Scripts | |
| id: setup | |
| uses: github/gh-aw-actions/setup@ff0525b685481744f490a0d362753d8001e4b39d # v0.72.0 | |
| with: | |
| destination: ${{ runner.temp }}/gh-aw/actions | |
| job-name: ${{ github.job }} | |
| trace-id: ${{ needs.activation.outputs.setup-trace-id }} | |
| env: | |
| GH_AW_SETUP_WORKFLOW_NAME: "PR Documentation Check" | |
| GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/pr-docs-check.lock.yml@${{ github.ref }} | |
| GH_AW_INFO_VERSION: "1.0.40" | |
| - name: Download agent output artifact | |
| id: download-agent-output | |
| continue-on-error: true | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: agent | |
| path: /tmp/gh-aw/ | |
| - name: Setup agent output environment variable | |
| id: setup-agent-output-env | |
| if: steps.download-agent-output.outcome == 'success' | |
| run: | | |
| mkdir -p /tmp/gh-aw/ | |
| find "/tmp/gh-aw/" -type f -print | |
| echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" | |
| - name: Download patch artifact | |
| continue-on-error: true | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: agent | |
| path: /tmp/gh-aw/ | |
| - name: Generate GitHub App token | |
| id: safe-outputs-app-token | |
| uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 | |
| with: | |
| client-id: ${{ secrets.ASPIRE_BOT_APP_ID }} | |
| private-key: ${{ secrets.ASPIRE_BOT_PRIVATE_KEY }} | |
| owner: microsoft | |
| repositories: |- | |
| aspire.dev | |
| aspire | |
| github-api-url: ${{ github.api_url }} | |
| permission-contents: write | |
| permission-issues: write | |
| permission-pull-requests: write | |
| - name: Extract base branch from agent output | |
| id: extract-base-branch | |
| if: steps.download-agent-output.outcome == 'success' | |
| shell: bash | |
| run: | | |
| if [ -f "/tmp/gh-aw/agent_output.json" ]; then | |
| GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) | |
| BASE_BRANCH=$("$GH_AW_NODE" -e " | |
| try { | |
| const data = JSON.parse(require('fs').readFileSync('/tmp/gh-aw/agent_output.json', 'utf8')); | |
| const item = (data.items || []).find(i => | |
| (i.type === 'create_pull_request' || i.type === 'push_to_pull_request_branch') && | |
| i.base_branch | |
| ); | |
| if (item) process.stdout.write(item.base_branch); | |
| } catch(e) {} | |
| " 2>/dev/null || true) | |
| # Validate: only allow safe git branch name characters | |
| if [[ "$BASE_BRANCH" =~ ^[a-zA-Z0-9/_.-]+$ ]] && [ ${#BASE_BRANCH} -le 255 ]; then | |
| printf 'base-branch=%s\n' "$BASE_BRANCH" >> "$GITHUB_OUTPUT" | |
| echo "Extracted base branch from safe output: $BASE_BRANCH" | |
| fi | |
| fi | |
| - name: Checkout repository | |
| if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| repository: microsoft/aspire.dev | |
| ref: main | |
| token: ${{ steps.safe-outputs-app-token.outputs.token }} | |
| persist-credentials: false | |
| fetch-depth: 1 | |
| - name: Configure Git credentials | |
| if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') | |
| env: | |
| REPO_NAME: "microsoft/aspire.dev" | |
| SERVER_URL: ${{ github.server_url }} | |
| GIT_TOKEN: ${{ steps.safe-outputs-app-token.outputs.token }} | |
| run: | | |
| git config --global user.email "github-actions[bot]@users.noreply.github.com" | |
| git config --global user.name "github-actions[bot]" | |
| git config --global am.keepcr true | |
| # Re-authenticate git with GitHub token | |
| SERVER_URL_STRIPPED="${SERVER_URL#https://}" | |
| git remote set-url origin "https://x-access-token:${GIT_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" | |
| echo "Git configured with standard GitHub Actions identity" | |
| - name: Configure GH_HOST for enterprise compatibility | |
| id: ghes-host-config | |
| shell: bash | |
| run: | | |
| # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct | |
| # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. | |
| GH_HOST="${GITHUB_SERVER_URL#https://}" | |
| GH_HOST="${GH_HOST#http://}" | |
| echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" | |
| - name: Mirror target repo checkout | |
| if: contains(needs.agent.outputs.output_types, 'create_pull_request') | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| fetch-depth: 1 | |
| path: _repos/aspire.dev | |
| persist-credentials: false | |
| ref: main | |
| repository: microsoft/aspire.dev | |
| token: ${{ steps.safe-outputs-app-token.outputs.token }} | |
| - name: Configure mirrored target repo Git credentials | |
| if: contains(needs.agent.outputs.output_types, 'create_pull_request') | |
| run: | | |
| git config --global user.email "github-actions[bot]@users.noreply.github.com" | |
| git config --global user.name "github-actions[bot]" | |
| git config --global am.keepcr true | |
| SERVER_URL_STRIPPED="${SERVER_URL#https://}" | |
| git remote set-url origin "https://x-access-token:${GIT_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" | |
| echo "Mirrored checkout configured with standard GitHub Actions identity" | |
| env: | |
| GIT_TOKEN: ${{ steps.safe-outputs-app-token.outputs.token }} | |
| REPO_NAME: microsoft/aspire.dev | |
| SERVER_URL: ${{ github.server_url }} | |
| working-directory: _repos/aspire.dev | |
| - name: Process Safe Outputs | |
| id: process_safe_outputs | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} | |
| GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,docs.github.com,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" | |
| GITHUB_SERVER_URL: ${{ github.server_url }} | |
| GITHUB_API_URL: ${{ github.api_url }} | |
| GH_AW_SAFE_OUTPUT_JOBS: "{\"notify_source_pr\":\"\"}" | |
| GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_pull_request\":{\"allowed_base_branches\":[\"main\",\"release/*\"],\"base_branch\":\"main\",\"draft\":true,\"fallback_as_issue\":true,\"labels\":[\"docs-from-code\"],\"max\":1,\"max_patch_files\":100,\"max_patch_size\":1024,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"protected_files_policy\":\"blocked\",\"target-repo\":\"microsoft/aspire.dev\",\"title_prefix\":\"[docs] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" | |
| GH_AW_CI_TRIGGER_TOKEN: ${{ secrets.GH_AW_CI_TRIGGER_TOKEN }} | |
| GITHUB_TOKEN: ${{ steps.safe-outputs-app-token.outputs.token }} | |
| with: | |
| github-token: ${{ steps.safe-outputs-app-token.outputs.token }} | |
| script: | | |
| const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); | |
| setupGlobals(core, github, context, exec, io, getOctokit); | |
| const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs'); | |
| await main(); | |
| - name: Invalidate GitHub App token | |
| if: always() && steps.safe-outputs-app-token.outputs.token != '' | |
| env: | |
| TOKEN: ${{ steps.safe-outputs-app-token.outputs.token }} | |
| run: | | |
| echo "Revoking GitHub App installation token..." | |
| # GitHub CLI will auth with the token being revoked. | |
| gh api \ | |
| --method DELETE \ | |
| -H "Authorization: token $TOKEN" \ | |
| /installation/token || echo "Token revoke may already be expired." | |
| echo "Token invalidation step complete." | |
| - name: Upload Safe Outputs Items | |
| if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: safe-outputs-items | |
| path: | | |
| /tmp/gh-aw/safe-output-items.jsonl | |
| /tmp/gh-aw/temporary-id-map.json | |
| if-no-files-found: ignore | |