From b9ed9e6c6eb50dc3f748e050ce5de6cfe0cca7a7 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Thu, 25 Jun 2026 18:58:21 +0200 Subject: [PATCH 1/7] auto-api-docs-writer: switch to direct-XML pipeline with per-role models MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rewrite the daily writer to match the upgraded api-docs skill, which edits the mdoc XML directly instead of the extract→write→merge JSON round-trip. Host steps: - Drop the "Extract placeholders and manifest" and "Upload extracted JSON" steps from regenerate-stubs, the "Download pre-extracted JSON" / "Save original JSON" pre-agent steps, and the "Save final JSON" post-step. None are needed without the JSON layer. - Add a non-fatal "Bootstrap SkiaSharp binding for snippet checks" pre-agent step (externals-download + dotnet build binding/SkiaSharp) so the example reviewer can compile-check snippets against a real SkiaSharp.dll. - Keep the SkiaSharpAPI symlink (for the host docs-format-docs post-step) and the formatting post-step. Agent prompt (runtime-imported body): - Replace the JSON phases with the skill's direct-XML add pipeline: resolve-scope new -> writer edits XML -> lint + 3 reviewers + synthesizer -> fix CRITICAL -> structural validate -> commit + PR. - Route each sub-agent through the task tool's per-role model (writer/factual/ examples = opus, quality/synthesizer = sonnet), with an engine.model fallback if the sandbox does not honor per-sub-agent models. - Export DOCS_GIT_ROOT/DOCS_DIR on docs-tool.ps1 calls so scope/validate use the docs repo (primary checkout) for git baselines while source lookups still use the SkiaSharp clone. Recompiled the .lock.yml via `gh aw compile`. Note: depends on the matching api-docs skill update in mono/SkiaSharp (the cloned skiasharp_branch must carry the new skill + docs-tool.ps1 DOCS_GIT_ROOT support). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../workflows/auto-api-docs-writer.lock.yml | 62 +++----- .github/workflows/auto-api-docs-writer.md | 147 +++++++++++------- 2 files changed, 109 insertions(+), 100 deletions(-) diff --git a/.github/workflows/auto-api-docs-writer.lock.yml b/.github/workflows/auto-api-docs-writer.lock.yml index da79936..9d32b96 100644 --- a/.github/workflows/auto-api-docs-writer.lock.yml +++ b/.github/workflows/auto-api-docs-writer.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"d74377dfeb3220592de0b044555f57b6f2758da8ebffb69185355672984d84df","compiler_version":"v0.71.5","strict":true,"agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"cbace7d3317f0dbef709f125cdc6f4107a1b49b73f1778d113159121fd09e1d9","compiler_version":"v0.71.5","strict":true,"agent_id":"copilot"} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache","sha":"0057852bfaa89a56745cba8c7296529d2fc39830","version":"v4"},{"repo":"actions/checkout","sha":"34e114876b0b11c390a56381ad16ebd13914f8d5","version":"v4"},{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/download-artifact","sha":"d3f86a106a0bac45b974a628896c90dbdf5c8093","version":"v4"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-dotnet","sha":"67a3573c9a986a3f9c594539f4ab511d57bb3ce9","version":"v4"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"actions/upload-artifact","sha":"ea165f8d65b6e75b540449e92b4886f43607fa02","version":"v4"},{"repo":"github/gh-aw-actions/setup","sha":"b8068426813005612b960b5ab0b8bd2c27142323","version":"v0.71.5"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.40","digest":"sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.40@sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40","digest":"sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40@sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.40","digest":"sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.40@sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51"},{"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"}]} # ___ _ _ # / _ \ | | (_) @@ -22,7 +22,7 @@ # # For more information: https://github.github.com/gh-aw/introduction/overview/ # -# Daily API documentation pipeline — regenerates XML stubs from CI NuGets, then AI fills 'To be added.' placeholders. +# Daily API documentation pipeline — regenerates XML stubs from CI NuGets, then AI fills 'To be added.' placeholders by editing the mdoc XML directly. # # Secrets used: # - COPILOT_GITHUB_TOKEN @@ -201,23 +201,23 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_a9cbb40a4aa95c57_EOF' + cat << 'GH_AW_PROMPT_c2191c4c1abba5bd_EOF' - GH_AW_PROMPT_a9cbb40a4aa95c57_EOF + GH_AW_PROMPT_c2191c4c1abba5bd_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_a9cbb40a4aa95c57_EOF' + cat << 'GH_AW_PROMPT_c2191c4c1abba5bd_EOF' Tools: create_pull_request, missing_tool, missing_data, noop - GH_AW_PROMPT_a9cbb40a4aa95c57_EOF + GH_AW_PROMPT_c2191c4c1abba5bd_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" - cat << 'GH_AW_PROMPT_a9cbb40a4aa95c57_EOF' + cat << 'GH_AW_PROMPT_c2191c4c1abba5bd_EOF' - GH_AW_PROMPT_a9cbb40a4aa95c57_EOF + GH_AW_PROMPT_c2191c4c1abba5bd_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_a9cbb40a4aa95c57_EOF' + cat << 'GH_AW_PROMPT_c2191c4c1abba5bd_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -249,12 +249,12 @@ jobs: - **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). - GH_AW_PROMPT_a9cbb40a4aa95c57_EOF + GH_AW_PROMPT_c2191c4c1abba5bd_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_a9cbb40a4aa95c57_EOF' + cat << 'GH_AW_PROMPT_c2191c4c1abba5bd_EOF' {{#runtime-import .github/workflows/auto-api-docs-writer.md}} - GH_AW_PROMPT_a9cbb40a4aa95c57_EOF + GH_AW_PROMPT_c2191c4c1abba5bd_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -441,19 +441,16 @@ jobs: with: name: docs-regenerated path: SkiaSharpAPI/ - - name: Download pre-extracted JSON - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 - with: - name: docs-extracted - path: output/docs-work/ - env: SKIASHARP_BRANCH: ${{ inputs.skiasharp_branch || 'main' }} name: Clone SkiaSharp (shallow, with submodules) run: "git clone --depth 1 --branch \"$SKIASHARP_BRANCH\" \\\n --recurse-submodules --shallow-submodules \\\n https://github.com/mono/SkiaSharp.git skiasharp\nmkdir -p skiasharp/docs\nln -sfn \"$(pwd)/SkiaSharpAPI\" skiasharp/docs/SkiaSharpAPI\ncd skiasharp && dotnet tool restore\n" - - name: Save original JSON to agent artifact cache + - continue-on-error: true + name: Bootstrap SkiaSharp binding for snippet checks run: |- - mkdir -p /tmp/gh-aw/agent/docs-work-original - cp -r output/docs-work/* /tmp/gh-aw/agent/docs-work-original/ + cd skiasharp + dotnet cake --target=externals-download + dotnet build binding/SkiaSharp/SkiaSharp.csproj -c Release - name: Download container images run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.40@sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40@sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280 ghcr.io/github/gh-aw-firewall/squid:0.25.40@sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51 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 @@ -462,9 +459,9 @@ jobs: 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_0f9919760fb1f6c1_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_95bfbea592eaca2e_EOF' {"create_pull_request":{"base_branch":"main","draft":false,"max":1,"max_patch_files":100,"max_patch_size":1024,"preserve_branch_name":true,"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"],"recreate_ref":true},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_0f9919760fb1f6c1_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_95bfbea592eaca2e_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -670,7 +667,7 @@ jobs: 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_dc4a8f8b337ab7b3_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_f0ffca7316016fa8_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -718,7 +715,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_dc4a8f8b337ab7b3_EOF + GH_AW_MCP_CONFIG_f0ffca7316016fa8_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -911,10 +908,6 @@ jobs: if [ ! -f /tmp/gh-aw/agent_output.json ]; then echo '{"items":[]}' > /tmp/gh-aw/agent_output.json fi - - name: Save final JSON to agent artifact cache - run: | - mkdir -p /tmp/gh-aw/agent/docs-work-final - cp -r output/docs-work/* /tmp/gh-aw/agent/docs-work-final/ - name: Format docs run: cd skiasharp && dotnet cake --target=docs-format-docs @@ -1182,7 +1175,7 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: WORKFLOW_NAME: "Auto API Docs Writer" - WORKFLOW_DESCRIPTION: "Daily API documentation pipeline — regenerates XML stubs from CI NuGets, then AI fills 'To be added.' placeholders." + WORKFLOW_DESCRIPTION: "Daily API documentation pipeline — regenerates XML stubs from CI NuGets, then AI fills 'To be added.' placeholders by editing the mdoc XML directly." HAS_PATCH: ${{ needs.agent.outputs.has_patch }} with: script: | @@ -1362,23 +1355,12 @@ jobs: docs-package-cache- - name: Regenerate API docs run: bash scripts/infra/docs/generate-api-docs.sh - - name: Extract placeholders and manifest - run: | - New-Item -ItemType Directory -Path output/docs-work -Force | Out-Null - & .agents/skills/api-docs/scripts/docs-tool.ps1 extract docs/SkiaSharpAPI/ -Output output/docs-work/ - shell: pwsh - name: Upload regenerated docs uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: docs-regenerated path: docs/SkiaSharpAPI/ retention-days: 1 - - name: Upload extracted JSON (immutable baseline) - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 - with: - name: docs-extracted - path: output/docs-work/ - retention-days: 7 safe_outputs: needs: diff --git a/.github/workflows/auto-api-docs-writer.md b/.github/workflows/auto-api-docs-writer.md index 3b089a8..07c04f3 100644 --- a/.github/workflows/auto-api-docs-writer.md +++ b/.github/workflows/auto-api-docs-writer.md @@ -1,5 +1,5 @@ --- -description: "Daily API documentation pipeline — regenerates XML stubs from CI NuGets, then AI fills 'To be added.' placeholders." +description: "Daily API documentation pipeline — regenerates XML stubs from CI NuGets, then AI fills 'To be added.' placeholders by editing the mdoc XML directly." # -- Triggers ---------------------------------------------------------- on: @@ -71,23 +71,12 @@ jobs: docs-package-cache- - name: Regenerate API docs run: bash scripts/infra/docs/generate-api-docs.sh - - name: Extract placeholders and manifest - shell: pwsh - run: | - New-Item -ItemType Directory -Path output/docs-work -Force | Out-Null - & .agents/skills/api-docs/scripts/docs-tool.ps1 extract docs/SkiaSharpAPI/ -Output output/docs-work/ - name: Upload regenerated docs uses: actions/upload-artifact@v4 with: name: docs-regenerated path: docs/SkiaSharpAPI/ retention-days: 1 - - name: Upload extracted JSON (immutable baseline) - uses: actions/upload-artifact@v4 - with: - name: docs-extracted - path: output/docs-work/ - retention-days: 7 # -- Checkout ---------------------------------------------------------- # Primary: this docs repo only. SkiaSharp is cloned in pre-agent-steps. @@ -134,12 +123,6 @@ pre-agent-steps: name: docs-regenerated path: SkiaSharpAPI/ - - name: Download pre-extracted JSON - uses: actions/download-artifact@v4 - with: - name: docs-extracted - path: output/docs-work/ - - name: Clone SkiaSharp (shallow, with submodules) env: SKIASHARP_BRANCH: ${{ inputs.skiasharp_branch || 'main' }} @@ -151,56 +134,91 @@ pre-agent-steps: ln -sfn "$(pwd)/SkiaSharpAPI" skiasharp/docs/SkiaSharpAPI cd skiasharp && dotnet tool restore - - name: Save original JSON to agent artifact cache + # Build the managed binding so the example reviewer can compile-check snippets + # against a real SkiaSharp.dll. C#-only — externals-download fetches prebuilt + # natives (no native changes here). Non-fatal: a build hiccup must not sink the + # whole docs run, since reviewers also verify examples by reading source. + - name: Bootstrap SkiaSharp binding for snippet checks + continue-on-error: true run: | - mkdir -p /tmp/gh-aw/agent/docs-work-original - cp -r output/docs-work/* /tmp/gh-aw/agent/docs-work-original/ + cd skiasharp + dotnet cake --target=externals-download + dotnet build binding/SkiaSharp/SkiaSharp.csproj -c Release # -- Post-agent steps (host) ------------------------------------------ -# Format docs AFTER the agent merges JSON→XML. Runs on host outside the +# Format docs AFTER the agent edits the XML in place. Runs on host outside the # sandbox so it has full access to the SkiaSharp cake scripts. post-steps: - - name: Save final JSON to agent artifact cache - run: | - mkdir -p /tmp/gh-aw/agent/docs-work-final - cp -r output/docs-work/* /tmp/gh-aw/agent/docs-work-final/ - - name: Format docs run: cd skiasharp && dotnet cake --target=docs-format-docs --- # Auto API Docs Writer -**Read `skiasharp/.agents/skills/api-docs/SKILL.md` for reference.** Follow the phases below — this workflow pre-computes Phases 1–2, so start at Phase 3. +You are the **orchestrator** for the `add` workflow. Read these first, then drive the phases below: + +- `skiasharp/.agents/skills/api-docs/SKILL.md` — the router. +- `skiasharp/.agents/skills/api-docs/workflows/add.md` — the direct-XML add pipeline you are running. +- `skiasharp/.agents/skills/api-docs/workflows/review.md` — the review pass **and the per-role model table**. +- `skiasharp/.agents/skills/api-docs/workflows/validation.md` — the post-edit gates. + +The stub regeneration (mdoc) already ran as a pre-step, so the placeholder `*.xml` files are present in +`SkiaSharpAPI/` as uncommitted working-tree changes. There is **no extract/merge JSON step** — agents read +and **edit the mdoc XML directly**; safety comes from the structural validator, not a merge guard. + +## Model routing + +You run on the default `engine.model` (cheap orchestrator). You do **not** write docs yourself. Launch every +sub-agent **via the `task` tool with an explicit `model`**, reading the per-role value from the table in +`workflows/review.md` and each agent file's `Model:` header (writer + factual + examples → `claude-opus-4.6`; +quality + synthesizer → `claude-sonnet-4.6`). If the sandbox does not honor per-sub-agent `model` (the parent +overrides it), proceed on `engine.model` for all roles and note it in the PR body — do not abort. + +## Scope environment + +`docs-tool.ps1` lives in the SkiaSharp clone, so by default it would look for docs under +`skiasharp/docs`. In this workflow the **docs repo is the primary checkout** and the regenerated XML is +in `SkiaSharpAPI/` at the workspace root. Point the tool at it by exporting these on every +`docs-tool.ps1` call (they make `resolve-scope new`, `lint`, and `validate` use the docs repo for git +baselines/diffs while source lookups still use the SkiaSharp clone): + +```bash +DOCS_GIT_ROOT="$GITHUB_WORKSPACE" DOCS_DIR="$GITHUB_WORKSPACE/SkiaSharpAPI" +``` ## Execution order -1. **Phase 3 (Discover — lightweight)** — you are an **orchestrator**, not a writer. Read ONLY: - - `output/docs-work/manifest.json` — file list and field counts - - `skiasharp/.agents/skills/api-docs/references/patterns.md` — formatting rules - - `skiasharp/.agents/skills/api-docs/references/skia-patterns.md` — domain knowledge - - **Do NOT pre-read JSON files or source code.** The writer agent handles its own discovery. Move to Phase 4 immediately after reading the manifest and references. - -2. **Phase 4 (Write — 1 agent)** — launch **1** background `general-purpose` agent: - - Use the writer prompt from SKILL.md Phase 4 - - The single writer reads ALL JSON files + corresponding C# source and fills documentation - - Wait for the writer to complete before Phase 5 - -3. **Phase 5 (Review — 3 independent agents)** — launch **three** background `general-purpose` agents in parallel as described in SKILL.md Phase 5: - - **Factual Claim Verifier** — reads source FIRST, then challenges every factual claim - - **Code Example Verifier** — verifies every code example uses real APIs - - **Quality Reviewer** — checks style, completeness, and patterns - - Wait for all three to complete, then fix all CRITICAL issues directly in the JSON files. - -4. **Phase 6 (Merge)** — this is the critical step. Run: +1. **Discover (lightweight).** Resolve the placeholder files into an explicit list and shard it into + ~25–40-file batches. Do **not** pre-read source or XML — the writer does its own discovery. + ```bash + cd skiasharp && DOCS_GIT_ROOT="$GITHUB_WORKSPACE" DOCS_DIR="$GITHUB_WORKSPACE/SkiaSharpAPI" \ + pwsh .agents/skills/api-docs/scripts/docs-tool.ps1 resolve-scope new && cd .. + ``` + +2. **Write (per batch).** For each batch, launch the **writer** sub-agent (`agents/writer.md`, model from its + header) with the resolved file list. It reads the C# source, fills only the empty/`To be added.` fields, + and edits the XML in place. A type it cannot document with certainty keeps its placeholder (a `DEFERRED` + line) so the next run re-detects it. + +3. **Review (per batch).** Run the deterministic linter, then launch the **three** reviewers in parallel + (`reviewer-factual`, `reviewer-examples`, `reviewer-quality`), each on the batch's files with its assigned + model. Feed all findings to `review-synthesizer`. + ```bash + cd skiasharp && DOCS_GIT_ROOT="$GITHUB_WORKSPACE" DOCS_DIR="$GITHUB_WORKSPACE/SkiaSharpAPI" \ + pwsh .agents/skills/api-docs/scripts/docs-tool.ps1 lint new && cd .. + ``` + +4. **Fix CRITICAL findings** by editing the XML directly. Skip MINOR/style for the automated run. + +5. **Validate (replaces merge).** This is the gate that makes direct editing safe — it must pass before the PR: ```bash - cd skiasharp && pwsh .agents/skills/api-docs/scripts/docs-tool.ps1 merge ../output/docs-work/ && cd .. + cd skiasharp && DOCS_GIT_ROOT="$GITHUB_WORKSPACE" DOCS_DIR="$GITHUB_WORKSPACE/SkiaSharpAPI" \ + pwsh .agents/skills/api-docs/scripts/docs-tool.ps1 validate new && cd .. ``` - Do NOT run `docs-format-docs` — it runs automatically as a post-step after the agent finishes. + It asserts each changed file is well-formed, has unchanged signature counts, and changed **only** inside + ``. Do **not** run `docs-format-docs` — formatting runs automatically as a post-step. -5. **Commit and PR** — commit the XML changes and create a pull request: +6. **Commit and PR.** Commit the XML changes and open a pull request: ```bash git checkout -b automation/write-api-docs git add SkiaSharpAPI/ @@ -211,20 +229,29 @@ post-steps: - Title: `Fill API documentation placeholders` - Body: `Automated AI-generated documentation for XML API docs with 'To be added.' placeholders.` -If there are no documentation changes after merging, call the `noop` tool instead. +If there are no documentation changes after validation, call the `noop` tool instead. ## Critical rules -- **Sub-agents must NOT spawn their own sub-agents.** Each agent (writer and reviewers) must do all its work directly. Nested sub-agents hit the depth limit and cause timeouts. -- **Do NOT edit XML files directly** — edit only the JSON files in `output/docs-work/`. -- **Phase 6 MUST run.** If you skip the merge, no PR is created and the entire run is wasted. +- **Edit the mdoc XML directly.** There is no JSON round-trip. Touch only `` content — never + `MemberSignature`/`TypeSignature`, attributes, or generated files (`index.xml`, `ns-*.xml`, `_filter.xml`, + `FrameworksIndex/`). The structural validator enforces this; a failure means you edited outside ``. +- **Step 5 (validate) MUST pass before the PR.** If you skip it, a malformed or surface-changing edit can ship. +- **Sub-agents must NOT spawn their own sub-agents.** Each agent does all its work directly — nested sub-agents + hit the depth limit and time out. - **Do NOT run `docs-format-docs`** — formatting runs automatically as a post-step. -- **Budget awareness:** After the writer completes and reviewers report, fix CRITICAL issues and proceed to merge+PR immediately. Do not re-run reviewers unless absolutely necessary. **If you're past 10 minutes and haven't merged yet, skip Phase 5 (review) entirely and go straight to Phase 6 (merge) + PR. A PR without review is better than no PR.** -- **NEVER end a turn without a tool call while waiting for agents.** When you launch a background agent, you MUST call `read_agent` with `wait: true` in the SAME response. Do NOT output text saying "waiting" and end your turn — the session WILL terminate and all work is lost. +- **Budget awareness:** Fix CRITICAL findings and proceed to validate + PR promptly. Do not re-run reviewers + unless necessary. **If you're past 10 minutes and haven't reached step 5, skip review (step 3) entirely and + go straight to validate + PR.** A validated PR without review beats no PR — but never skip step 5. +- **NEVER end a turn without a tool call while waiting for agents.** When you launch a background agent, you + MUST call `read_agent` with `wait: true` in the SAME response. Outputting "waiting" and ending the turn + terminates the session and loses all work. - **Single agent:** `task(background)` + `read_agent(id, wait=true)` in the same response. - - **Multiple agents:** Launch all agents, then call `read_agent` for THE FIRST ONE with `wait: true`. When it returns, call `read_agent` for the next, and so on. You MUST have an active `read_agent` call at all times until all agents complete. - - **FORBIDDEN pattern:** Launching agents → saying "Waiting for them to complete" → ending turn. This KILLS the session. -- **COMPLETION GATE:** Your session is NOT complete until you have called `create_pull_request` or `noop`. If you reach a point where you think you're done but haven't called either, something went wrong — retrace your steps and complete the remaining phases. + - **Multiple agents:** launch all, then `read_agent` the first with `wait: true`; when it returns, read the + next, and so on. Keep an active `read_agent` call at all times until all agents complete. + - **FORBIDDEN:** launching agents → "Waiting for them to complete" → ending the turn. This KILLS the session. +- **COMPLETION GATE:** Your session is NOT complete until you have called `create_pull_request` or `noop`. If + you think you're done but called neither, retrace your steps and finish the remaining phases. ## Path differences from SKILL.md From 617b8a359472dbca77e9f2e05d99780f7ac2ad19 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Thu, 25 Jun 2026 19:51:10 +0200 Subject: [PATCH 2/7] auto-api-docs-writer: route factual reviewer to gpt-5.5 (eval bake-off) The api-docs eval per-role bake-off picked gpt-5.5 for reviewer-factual (only candidate that caught the seeded byte-order contradiction; best mean recall + precision). Align the workflow's per-role routing note with the skill's updated review.md table and reviewer-factual Model: header. Body-only change (runtime-imported), so the compiled .lock.yml is unaffected. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/auto-api-docs-writer.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/auto-api-docs-writer.md b/.github/workflows/auto-api-docs-writer.md index 07c04f3..0355fa8 100644 --- a/.github/workflows/auto-api-docs-writer.md +++ b/.github/workflows/auto-api-docs-writer.md @@ -170,9 +170,10 @@ and **edit the mdoc XML directly**; safety comes from the structural validator, You run on the default `engine.model` (cheap orchestrator). You do **not** write docs yourself. Launch every sub-agent **via the `task` tool with an explicit `model`**, reading the per-role value from the table in -`workflows/review.md` and each agent file's `Model:` header (writer + factual + examples → `claude-opus-4.6`; -quality + synthesizer → `claude-sonnet-4.6`). If the sandbox does not honor per-sub-agent `model` (the parent -overrides it), proceed on `engine.model` for all roles and note it in the PR body — do not abort. +`workflows/review.md` and each agent file's `Model:` header (writer + examples → `claude-opus-4.6`; +factual → `gpt-5.5` per the eval bake-off; quality + synthesizer → `claude-sonnet-4.6`). If the sandbox does +not honor per-sub-agent `model` (the parent overrides it), proceed on `engine.model` for all roles and note +it in the PR body — do not abort. ## Scope environment From d28641ae5e2dff3b380f0ce4c943044973c383ec Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Thu, 25 Jun 2026 20:12:49 +0200 Subject: [PATCH 3/7] Add on-demand auto-api-docs-reviewer workflow (review mode + scope input) Sibling of auto-api-docs-writer that runs the api-docs skill's REVIEW pipeline on a scope (default group:text) instead of filling placeholders. Doubles as the first CI exercise of per-sub-agent model routing: the orchestrator launches each reviewer/writer sub-agent via the task tool with an explicit model (factual -> gpt-5.5 per the eval bake-off) and prints a Routing report so the run is auditable. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../workflows/auto-api-docs-reviewer.lock.yml | 1381 +++++++++++++++++ .github/workflows/auto-api-docs-reviewer.md | 251 +++ 2 files changed, 1632 insertions(+) create mode 100644 .github/workflows/auto-api-docs-reviewer.lock.yml create mode 100644 .github/workflows/auto-api-docs-reviewer.md diff --git a/.github/workflows/auto-api-docs-reviewer.lock.yml b/.github/workflows/auto-api-docs-reviewer.lock.yml new file mode 100644 index 0000000..78203f4 --- /dev/null +++ b/.github/workflows/auto-api-docs-reviewer.lock.yml @@ -0,0 +1,1381 @@ +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"c9bdf2f15d1adef06f531a5d16cfd383ab678225f01a855a87f35276092d0ca4","compiler_version":"v0.71.5","strict":true,"agent_id":"copilot"} +# gh-aw-manifest: {"version":1,"secrets":["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/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"b8068426813005612b960b5ab0b8bd2c27142323","version":"v0.71.5"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.40","digest":"sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.40@sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40","digest":"sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40@sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.40","digest":"sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.40@sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51"},{"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.71.5). 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/ +# +# On-demand API documentation review — audits EXISTING mdoc XML for a scope (default the text/font slice), fixes CRITICAL + obsolete-example findings by editing the XML directly, and opens a PR. Doubles as the CI exercise of per-sub-agent model routing. +# +# Secrets used: +# - 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/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 +# - 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@b8068426813005612b960b5ab0b8bd2c27142323 # v0.71.5 +# +# Container images used: +# - ghcr.io/github/gh-aw-firewall/agent:0.25.40@sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40@sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280 +# - ghcr.io/github/gh-aw-firewall/squid:0.25.40@sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51 +# - 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: "Auto API Docs Reviewer" +"on": + workflow_dispatch: + inputs: + aw_context: + default: "" + description: Agent caller context (used internally by Agentic Workflows). + required: false + type: string + scope: + default: group:text + description: "Review scope selector (docs-tool.ps1 grammar): group:text, type:SKFont, ns:HarfBuzzSharp, all, changed, match:font" + required: false + type: string + skiasharp_branch: + default: main + description: SkiaSharp branch to use for the skill, scripts, source x-ref, and binding build + required: false + type: string + +permissions: {} + +concurrency: + cancel-in-progress: true + group: auto-api-docs-reviewer + +run-name: "Auto API Docs Reviewer" + +jobs: + activation: + runs-on: ubuntu-slim + permissions: + actions: read + contents: read + outputs: + 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 }} + stale_lock_file_failed: ${{ steps.check-lock-file.outputs.stale_lock_file_failed == 'true' }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@b8068426813005612b960b5ab0b8bd2c27142323 # v0.71.5 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Auto API Docs Reviewer" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/auto-api-docs-reviewer.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.71.5" + GH_AW_INFO_WORKFLOW_NAME: "Auto API Docs Reviewer" + GH_AW_INFO_EXPERIMENTAL: "false" + GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" + GH_AW_INFO_STAGED: "false" + GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","github","dotnet"]' + GH_AW_INFO_FIREWALL_ENABLED: "true" + GH_AW_INFO_AWF_VERSION: "v0.25.40" + 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 workflow lock file + id: check-lock-file + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_WORKFLOW_FILE: "auto-api-docs-reviewer.lock.yml" + GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}" + 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_workflow_timestamp_api.cjs'); + await main(); + - name: Check compile-agentic version + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_COMPILED_VERSION: "v0.71.5" + 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: 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_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_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_d8bab8b7f9fbf0d1_EOF' + + GH_AW_PROMPT_d8bab8b7f9fbf0d1_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_d8bab8b7f9fbf0d1_EOF' + + Tools: create_pull_request, missing_tool, missing_data, noop + GH_AW_PROMPT_d8bab8b7f9fbf0d1_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" + cat << 'GH_AW_PROMPT_d8bab8b7f9fbf0d1_EOF' + + GH_AW_PROMPT_d8bab8b7f9fbf0d1_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" + cat << 'GH_AW_PROMPT_d8bab8b7f9fbf0d1_EOF' + + 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` → `__GH_AW_GITHUB_REPOSITORY__` (cwd) [shallow clone, fetch-depth=1] + - **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). + + + GH_AW_PROMPT_d8bab8b7f9fbf0d1_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" + cat << 'GH_AW_PROMPT_d8bab8b7f9fbf0d1_EOF' + + {{#runtime-import .github/workflows/auto-api-docs-reviewer.md}} + GH_AW_PROMPT_d8bab8b7f9fbf0d1_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" + 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_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_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' + 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_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_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 + } + }); + - 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/github_rate_limits.jsonl + /tmp/gh-aw/base + if-no-files-found: ignore + retention-days: 1 + + agent: + needs: activation + runs-on: ubuntu-latest + permissions: + contents: 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: autoapidocsreviewer + 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@b8068426813005612b960b5ab0b8bd2c27142323 # v0.71.5 + 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: "Auto API Docs Reviewer" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/auto-api-docs-reviewer.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: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + fetch-depth: 1 + - 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.40 + - 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" + - env: + REVIEW_SCOPE: ${{ inputs.scope || 'group:text' }} + name: Record review scope + run: "printf '%s' \"${REVIEW_SCOPE}\" > review-scope.txt\necho \"Review scope: $(cat review-scope.txt)\"\n" + - env: + SKIASHARP_BRANCH: ${{ inputs.skiasharp_branch || 'main' }} + name: Clone SkiaSharp (shallow, with submodules) + run: "git clone --depth 1 --branch \"$SKIASHARP_BRANCH\" \\\n --recurse-submodules --shallow-submodules \\\n https://github.com/mono/SkiaSharp.git skiasharp\nmkdir -p skiasharp/docs\nln -sfn \"$(pwd)/SkiaSharpAPI\" skiasharp/docs/SkiaSharpAPI\ncd skiasharp && dotnet tool restore\n" + - continue-on-error: true + name: Bootstrap SkiaSharp binding for snippet checks + run: |- + cd skiasharp + dotnet cake --target=externals-download + dotnet build binding/SkiaSharp/SkiaSharp.csproj -c Release + + - name: Download container images + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.40@sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40@sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280 ghcr.io/github/gh-aw-firewall/squid:0.25.40@sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51 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_c0b458da0c2dd9de_EOF' + {"create_pull_request":{"base_branch":"main","draft":false,"max":1,"max_patch_files":100,"max_patch_size":1024,"preserve_branch_name":true,"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"],"recreate_ref":true},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} + GH_AW_SAFE_OUTPUTS_CONFIG_c0b458da0c2dd9de_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." + }, + "repo_params": {}, + "dynamic_tools": [] + } + 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: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_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_2bf8dc3deb29f470_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" + }, + "guard-policies": { + "allow-only": { + "approval-labels": ${{ steps.parse-guard-vars.outputs.approval_labels }}, + "blocked-users": ${{ steps.parse-guard-vars.outputs.blocked_users }}, + "min-integrity": "none", + "repos": [ + "mono/skiasharp", + "mono/skiasharp-api-docs" + ], + "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:mono/skiasharp", + "private:mono/skiasharp-api-docs" + ] + } + } + } + }, + "gateway": { + "port": $MCP_GATEWAY_PORT, + "domain": "${MCP_GATEWAY_DOMAIN}", + "apiKey": "${MCP_GATEWAY_API_KEY}", + "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" + } + } + GH_AW_MCP_CONFIG_2bf8dc3deb29f470_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: 120 + 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.40/awf-config.schema.json","network":{"allowDomains":["*.githubusercontent.com","*.vsblob.vsassets.io","api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","api.nuget.org","api.snapcraft.io","archive.ubuntu.com","azure.archive.ubuntu.com","azuresearch-usnc.nuget.org","azuresearch-ussc.nuget.org","builds.dotnet.microsoft.com","ci.dot.net","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","dc.services.visualstudio.com","dist.nuget.org","docs.github.com","dot.net","dotnet.microsoft.com","dotnetcli.blob.core.windows.net","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","nuget.org","nuget.pkg.github.com","nugetregistryv2prod.blob.core.windows.net","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","oneocsp.microsoft.com","packagecloud.io","packages.cloud.google.com","packages.microsoft.com","pkgs.dev.azure.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","www.microsoft.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.40,squid=sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51,agent=sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504,api-proxy=sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280,cli-proxy=sha256:3e7152911d4b4b7b97beef9d3d7d924ff7902227e86001ef3838fb728d5d514c"}}' > "${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.71.5 + 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: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_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: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' + 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,*.vsblob.vsassets.io,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,ci.dot.net,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,dc.services.visualstudio.com,dist.nuget.org,docs.github.com,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,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,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,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,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pkgs.dev.azure.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,www.microsoft.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+r /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: Format docs + run: cd skiasharp && dotnet cake --target=docs-format-docs + + - 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 + + conclusion: + needs: + - activation + - agent + - detection + - 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-auto-api-docs-reviewer" + 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@b8068426813005612b960b5ab0b8bd2c27142323 # v0.71.5 + 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: "Auto API Docs Reviewer" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/auto-api-docs-reviewer.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: 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: "Auto API Docs Reviewer" + 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: ${{ 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/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: "Auto API Docs Reviewer" + 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: ${{ 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/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: "Auto API Docs Reviewer" + with: + github-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/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: "Auto API Docs Reviewer" + with: + github-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/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: "Auto API Docs Reviewer" + 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: "auto-api-docs-reviewer" + 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_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: "120" + with: + github-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/handle_agent_failure.cjs'); + await main(); + + 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@b8068426813005612b960b5ab0b8bd2c27142323 # v0.71.5 + 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: "Auto API Docs Reviewer" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/auto-api-docs-reviewer.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.40@sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40@sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280 ghcr.io/github/gh-aw-firewall/squid:0.25.40@sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51 + - 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: "Auto API Docs Reviewer" + WORKFLOW_DESCRIPTION: "On-demand API documentation review — audits EXISTING mdoc XML for a scope (default the text/font slice), fixes CRITICAL + obsolete-example findings by editing the XML directly, and opens a PR. Doubles as the CI exercise of per-sub-agent model routing." + 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.40 + - 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.40/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.40,squid=sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51,agent=sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504,api-proxy=sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280,cli-proxy=sha256:3e7152911d4b4b7b97beef9d3d7d924ff7902227e86001ef3838fb728d5d514c"}}' > "${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.71.5 + 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); + } + } + + 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 }}/auto-api-docs-reviewer" + 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: "auto-api-docs-reviewer" + GH_AW_WORKFLOW_NAME: "Auto API Docs Reviewer" + outputs: + 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@b8068426813005612b960b5ab0b8bd2c27142323 # v0.71.5 + 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: "Auto API Docs Reviewer" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/auto-api-docs-reviewer.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: 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: + ref: main + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_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: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + GIT_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.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:${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: 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,*.vsblob.vsassets.io,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,ci.dot.net,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,dc.services.visualstudio.com,dist.nuget.org,docs.github.com,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,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,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,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,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pkgs.dev.azure.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,www.microsoft.com" + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_pull_request\":{\"base_branch\":\"main\",\"draft\":false,\"max\":1,\"max_patch_files\":100,\"max_patch_size\":1024,\"preserve_branch_name\":true,\"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\"],\"recreate_ref\":true},\"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 }} + with: + github-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/safe_output_handler_manager.cjs'); + await main(); + - 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 + diff --git a/.github/workflows/auto-api-docs-reviewer.md b/.github/workflows/auto-api-docs-reviewer.md new file mode 100644 index 0000000..b63885a --- /dev/null +++ b/.github/workflows/auto-api-docs-reviewer.md @@ -0,0 +1,251 @@ +--- +description: "On-demand API documentation review — audits EXISTING mdoc XML for a scope (default the text/font slice), fixes CRITICAL + obsolete-example findings by editing the XML directly, and opens a PR. Doubles as the CI exercise of per-sub-agent model routing." + +# -- Triggers ---------------------------------------------------------- +# Dispatch-only: this is the periodic/on-demand REVIEW dual of the daily +# auto-api-docs-writer (which only fills `To be added.` placeholders). Review +# operates on already-filled docs, so there is no stub-regeneration job here. +on: + workflow_dispatch: + inputs: + skiasharp_branch: + description: "SkiaSharp branch to use for the skill, scripts, source x-ref, and binding build" + required: false + default: "main" + type: string + scope: + description: "Review scope selector (docs-tool.ps1 grammar): group:text, type:SKFont, ns:HarfBuzzSharp, all, changed, match:font" + required: false + default: "group:text" + type: string + +# -- Checkout ---------------------------------------------------------- +# Primary: this docs repo (its committed SkiaSharpAPI/*.xml is what we review). +# SkiaSharp is cloned in pre-agent-steps for the skill + C# source + binding. +# fetch-depth: 1 is sufficient — `validate` compares each edited file against the +# checked-out HEAD (the working-tree fallback in docs-tool.ps1), not origin/main. +checkout: + - fetch-depth: 1 +timeout-minutes: 120 +concurrency: + group: auto-api-docs-reviewer + cancel-in-progress: true + +# -- Agent tools ------------------------------------------------------- +tools: + github: + toolsets: [repos] + allowed-repos: ["mono/skiasharp", "mono/skiasharp-api-docs"] + min-integrity: none + bash: ["*"] + edit: + +# -- Network allowlist ------------------------------------------------- +network: + allowed: + - defaults + - github + - dotnet + +# -- Permissions ------------------------------------------------------- +permissions: + contents: read + +# -- Safe outputs ------------------------------------------------------ +safe-outputs: + create-pull-request: + draft: false + base-branch: main + preserve-branch-name: true + recreate-ref: true + +# -- Pre-agent steps (host) ------------------------------------------- +pre-agent-steps: + - name: Record review scope + env: + REVIEW_SCOPE: ${{ inputs.scope || 'group:text' }} + run: | + printf '%s' "${REVIEW_SCOPE}" > review-scope.txt + echo "Review scope: $(cat review-scope.txt)" + + - name: Clone SkiaSharp (shallow, with submodules) + env: + SKIASHARP_BRANCH: ${{ inputs.skiasharp_branch || 'main' }} + run: | + git clone --depth 1 --branch "$SKIASHARP_BRANCH" \ + --recurse-submodules --shallow-submodules \ + https://github.com/mono/SkiaSharp.git skiasharp + mkdir -p skiasharp/docs + ln -sfn "$(pwd)/SkiaSharpAPI" skiasharp/docs/SkiaSharpAPI + cd skiasharp && dotnet tool restore + + # Build the managed binding so reviewer-examples can compile-check snippets + # against a real SkiaSharp.dll. C#-only — externals-download fetches prebuilt + # natives (no native changes here). Non-fatal: a build hiccup must not sink the + # review, since the reviewer also verifies examples by reading source. + - name: Bootstrap SkiaSharp binding for snippet checks + continue-on-error: true + run: | + cd skiasharp + dotnet cake --target=externals-download + dotnet build binding/SkiaSharp/SkiaSharp.csproj -c Release + +# -- Post-agent steps (host) ------------------------------------------ +# Format docs AFTER the agent edits the XML in place. Runs on host outside the +# sandbox so it has full access to the SkiaSharp cake scripts. +post-steps: + - name: Format docs + run: cd skiasharp && dotnet cake --target=docs-format-docs +--- + +# Auto API Docs Reviewer + +You are the **orchestrator** for the `review` workflow — the dual of the writer. The writer fills blanks; +**you audit and improve docs that are already filled** for a scope, fix the high-severity issues, and open a +PR. Read these first, then drive the phases below: + +- `skiasharp/.agents/skills/api-docs/SKILL.md` — the router. +- `skiasharp/.agents/skills/api-docs/workflows/review.md` — the review pipeline **and the per-role model table**. +- `skiasharp/.agents/skills/api-docs/workflows/scope-resolution.md` — how the scope selector resolves to files. +- `skiasharp/.agents/skills/api-docs/workflows/validation.md` — the post-edit gates. + +The docs repo is the **primary checkout**; its committed `SkiaSharpAPI/*.xml` is what you review. There is +**no extract/merge JSON step** — you edit the mdoc XML directly; safety comes from the structural validator. + +## Why this run matters (it is also an eval) + +This workflow is the first real CI exercise of **per-sub-agent model routing**. You MUST launch every +sub-agent via the `task` tool with an explicit `model`, and you MUST report — in stdout and in the PR body — +which model you requested for each role and whether the sandbox honored it. See "Routing report" below. + +## Model routing + +You run on the default `engine.model` (cheap orchestrator). You do **not** review or write docs yourself. +Launch every sub-agent **via the `task` tool with an explicit `model`**, reading the per-role value from the +table in `workflows/review.md` and each agent file's `Model:` header: + +| Sub-agent | Model | +|---|---| +| reviewer-factual | `gpt-5.5` (eval bake-off winner) | +| reviewer-examples | `claude-opus-4.6` | +| reviewer-quality | `claude-sonnet-4.6` | +| review-synthesizer | `claude-sonnet-4.6` | +| writer (fix step) | `claude-opus-4.6` | + +If the sandbox does **not** honor per-sub-agent `model` (it errors, or forces the parent model), proceed on +`engine.model` for all roles and record that in the Routing report and PR body — **do not abort**. + +## Scope environment + +`docs-tool.ps1` lives in the SkiaSharp clone, so by default it looks for docs under `skiasharp/docs`. Here the +**docs repo is the primary checkout**, so point the tool at it by exporting these on every `docs-tool.ps1` +call (they make `resolve-scope`, `lint`, and `validate` use the docs repo for git baselines/diffs while source +lookups still use the SkiaSharp clone): + +```bash +DOCS_GIT_ROOT="$GITHUB_WORKSPACE" DOCS_DIR="$GITHUB_WORKSPACE/SkiaSharpAPI" +``` + +The review scope selector is in `review-scope.txt` at the workspace root (default `group:text`). + +## Execution order + +1. **Resolve scope.** Read the selector and resolve it to an explicit file list (fuzzy selectors need + `-Confirm:$false` in CI). The text/font slice is ~16 files — one batch; for larger scopes, shard into + ~25–40-file batches. + ```bash + SCOPE="$(cat review-scope.txt)" + cd skiasharp && DOCS_GIT_ROOT="$GITHUB_WORKSPACE" DOCS_DIR="$GITHUB_WORKSPACE/SkiaSharpAPI" \ + pwsh .agents/skills/api-docs/scripts/docs-tool.ps1 resolve-scope "$SCOPE" -Confirm:$false && cd .. + ``` + +2. **Lint (deterministic, no model).** Run the linter over the resolved files to catch objective defects. + ```bash + cd skiasharp && DOCS_GIT_ROOT="$GITHUB_WORKSPACE" DOCS_DIR="$GITHUB_WORKSPACE/SkiaSharpAPI" \ + pwsh .agents/skills/api-docs/scripts/docs-tool.ps1 lint "$SCOPE" && cd .. + ``` + +3. **Review (three reviewers in parallel).** Launch `reviewer-factual`, `reviewer-examples`, and + `reviewer-quality` via the `task` tool, each with its assigned model, on the resolved file list. They + **report only** (one `SEVERITY | class | file | docId | message` line each). Then feed the linter output + + all three reviewers' findings to `review-synthesizer` to dedupe/normalize. + +4. **Fix (gated, this run).** Edit the XML directly to resolve, in priority order: (a) all **CRITICAL** + findings, (b) **obsolete-in-example** findings (the text/font slice has legacy `paint.TextSize` / + `TextAlign` / `DrawText(string,x,y,paint)` examples — migrate them to `SKFont`), (c) where a central type is + example-poor (e.g. `SKFont`, `SKTypeface`, `SKPaint`), add one correct, compiling example, porting the + `SKCanvas`/`SKShader` quality bar. Launch the **writer** sub-agent for the edits. **Budget:** if you pass + ~60 minutes, stop fixing, validate what you have, and open the PR — a smaller validated PR beats none. + +5. **Validate (MUST pass before the PR).** This is the gate that makes direct editing safe: + ```bash + cd skiasharp && DOCS_GIT_ROOT="$GITHUB_WORKSPACE" DOCS_DIR="$GITHUB_WORKSPACE/SkiaSharpAPI" \ + pwsh .agents/skills/api-docs/scripts/docs-tool.ps1 validate "$SCOPE" && cd .. + ``` + It asserts each changed file is well-formed, has unchanged signature counts, and changed **only** inside + ``. Do **not** run `docs-format-docs` — formatting runs automatically as a post-step. + +6. **Routing report (ALWAYS).** Print to stdout a block delimited exactly like this, filled in from what the + `task` tool actually did, so the run is auditable even if no fixes were made: + ``` + === ROUTING REPORT === + reviewer-factual | requested: gpt-5.5 | honored: | note: ... + reviewer-examples | requested: claude-opus-4.6 | honored: | note: ... + reviewer-quality | requested: claude-sonnet-4.6| honored: | note: ... + review-synthesizer | requested: claude-sonnet-4.6| honored: | note: ... + writer | requested: claude-opus-4.6 | honored: | note: ... + === END ROUTING REPORT === + ``` + Base "honored" on observable evidence: whether the `task` call accepted the `model` parameter, any + model-not-supported error the sandbox surfaced, and any self-reported model from the sub-agent. If you had + to fall back to a single model for all roles, say so explicitly. + +7. **Commit and PR.** If step 4 produced edits: + ```bash + git checkout -b automation/review-api-docs + git add SkiaSharpAPI/ + git commit -m "Review and improve API documentation (scope: )" + ``` + Then use the `create_pull_request` tool: + - Branch: `automation/review-api-docs` + - Title: `Review and improve API documentation ()` + - Body: include (a) the scope reviewed and file count, (b) the **Routing report** block verbatim, (c) a + short **Findings summary** (counts by severity + the synthesizer's machine `FINDING |` block), and (d) + what you fixed vs deferred. + + If step 4 produced **no** edits (nothing CRITICAL/obsolete to fix), call the `noop` tool — but still print + the Routing report and Findings summary to stdout first so the eval is observable in the run logs. + +## Critical rules + +- **Edit the mdoc XML directly.** Touch only `` content — never `MemberSignature`/`TypeSignature`, + attributes, or generated files (`index.xml`, `ns-*.xml`, `_filter.xml`, `FrameworksIndex/`). The structural + validator enforces this; a failure means you edited outside ``. +- **Step 5 (validate) MUST pass before the PR.** If you skip it, a malformed or surface-changing edit can ship. +- **Sub-agents must NOT spawn their own sub-agents.** Each agent does all its work directly — nested sub-agents + hit the depth limit and time out. +- **Do NOT run `docs-format-docs`** — formatting runs automatically as a post-step. +- **Every code example you add or change must compile** against the real `SkiaSharp.dll` (bootstrapped in a + pre-step) and use **no obsolete members** (see `references/obsolete-api-map.md`). A non-compiling example is + worse than none. +- **NEVER end a turn without a tool call while waiting for agents.** When you launch a background agent, you + MUST call `read_agent` with `wait: true` in the SAME response. Outputting "waiting" and ending the turn + terminates the session and loses all work. + - **Single agent:** `task(background)` + `read_agent(id, wait=true)` in the same response. + - **Multiple agents:** launch all, then `read_agent` the first with `wait: true`; when it returns, read the + next, and so on. Keep an active `read_agent` call at all times until all agents complete. + - **FORBIDDEN:** launching agents → "Waiting for them to complete" → ending the turn. This KILLS the session. +- **COMPLETION GATE:** Your session is NOT complete until you have called `create_pull_request` or `noop`, and + you have printed the Routing report. If you think you're done but did neither, retrace your steps and finish. + +## Path differences from SKILL.md + +Because this workflow runs from the docs repo (not SkiaSharp), paths differ: + +| SKILL.md reference | Actual path in this workflow | +|---|---| +| `docs/SkiaSharpAPI/` | `SkiaSharpAPI/` (repo root) | +| `.agents/skills/api-docs/` | `skiasharp/.agents/skills/api-docs/` | +| `binding/SkiaSharp/` | `skiasharp/binding/SkiaSharp/` | +| `binding/HarfBuzzSharp/` | `skiasharp/binding/HarfBuzzSharp/` | +| `samples/Gallery/Shared/Samples/` | `skiasharp/samples/Gallery/Shared/Samples/` | From e535c65d1f949c2ccd06b26db327ce433352c22f Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Thu, 25 Jun 2026 20:22:54 +0200 Subject: [PATCH 4/7] auto-api-docs-writer: unify add + review (existing-docs pass + routing report) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make the daily writer the common path: after the add pass (fill placeholders) it now also runs a REVIEW pass over a baked scope of existing docs (review-scope.txt, default group:text) — lint + 3 reviewers + synthesizer + fix CRITICAL/obsolete via per-role task models — then one validate + PR covers both passes. Because the writer is already registered on the docs default branch, this is dispatchable on a feature branch (skiasharp_branch input) WITHOUT landing a new workflow on main; the review scope is baked (not a dispatch input) so input validation against the default branch still passes. Adds a mandatory Routing report so the run doubles as the first CI exercise of per-sub-agent model routing. Removes the now-redundant standalone auto-api-docs-reviewer workflow. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../workflows/auto-api-docs-reviewer.lock.yml | 1381 ----------------- .github/workflows/auto-api-docs-reviewer.md | 251 --- .../workflows/auto-api-docs-writer.lock.yml | 36 +- .github/workflows/auto-api-docs-writer.md | 120 +- 4 files changed, 116 insertions(+), 1672 deletions(-) delete mode 100644 .github/workflows/auto-api-docs-reviewer.lock.yml delete mode 100644 .github/workflows/auto-api-docs-reviewer.md diff --git a/.github/workflows/auto-api-docs-reviewer.lock.yml b/.github/workflows/auto-api-docs-reviewer.lock.yml deleted file mode 100644 index 78203f4..0000000 --- a/.github/workflows/auto-api-docs-reviewer.lock.yml +++ /dev/null @@ -1,1381 +0,0 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"c9bdf2f15d1adef06f531a5d16cfd383ab678225f01a855a87f35276092d0ca4","compiler_version":"v0.71.5","strict":true,"agent_id":"copilot"} -# gh-aw-manifest: {"version":1,"secrets":["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/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"b8068426813005612b960b5ab0b8bd2c27142323","version":"v0.71.5"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.40","digest":"sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.40@sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40","digest":"sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40@sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.40","digest":"sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.40@sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51"},{"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.71.5). 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/ -# -# On-demand API documentation review — audits EXISTING mdoc XML for a scope (default the text/font slice), fixes CRITICAL + obsolete-example findings by editing the XML directly, and opens a PR. Doubles as the CI exercise of per-sub-agent model routing. -# -# Secrets used: -# - 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/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 -# - 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@b8068426813005612b960b5ab0b8bd2c27142323 # v0.71.5 -# -# Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.25.40@sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504 -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40@sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280 -# - ghcr.io/github/gh-aw-firewall/squid:0.25.40@sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51 -# - 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: "Auto API Docs Reviewer" -"on": - workflow_dispatch: - inputs: - aw_context: - default: "" - description: Agent caller context (used internally by Agentic Workflows). - required: false - type: string - scope: - default: group:text - description: "Review scope selector (docs-tool.ps1 grammar): group:text, type:SKFont, ns:HarfBuzzSharp, all, changed, match:font" - required: false - type: string - skiasharp_branch: - default: main - description: SkiaSharp branch to use for the skill, scripts, source x-ref, and binding build - required: false - type: string - -permissions: {} - -concurrency: - cancel-in-progress: true - group: auto-api-docs-reviewer - -run-name: "Auto API Docs Reviewer" - -jobs: - activation: - runs-on: ubuntu-slim - permissions: - actions: read - contents: read - outputs: - 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 }} - stale_lock_file_failed: ${{ steps.check-lock-file.outputs.stale_lock_file_failed == 'true' }} - steps: - - name: Setup Scripts - id: setup - uses: github/gh-aw-actions/setup@b8068426813005612b960b5ab0b8bd2c27142323 # v0.71.5 - with: - destination: ${{ runner.temp }}/gh-aw/actions - job-name: ${{ github.job }} - env: - GH_AW_SETUP_WORKFLOW_NAME: "Auto API Docs Reviewer" - GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/auto-api-docs-reviewer.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.71.5" - GH_AW_INFO_WORKFLOW_NAME: "Auto API Docs Reviewer" - GH_AW_INFO_EXPERIMENTAL: "false" - GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" - GH_AW_INFO_STAGED: "false" - GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","github","dotnet"]' - GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.25.40" - 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 workflow lock file - id: check-lock-file - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_WORKFLOW_FILE: "auto-api-docs-reviewer.lock.yml" - GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}" - 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_workflow_timestamp_api.cjs'); - await main(); - - name: Check compile-agentic version - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_COMPILED_VERSION: "v0.71.5" - 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: 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_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_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_d8bab8b7f9fbf0d1_EOF' - - GH_AW_PROMPT_d8bab8b7f9fbf0d1_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_d8bab8b7f9fbf0d1_EOF' - - Tools: create_pull_request, missing_tool, missing_data, noop - GH_AW_PROMPT_d8bab8b7f9fbf0d1_EOF - cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" - cat << 'GH_AW_PROMPT_d8bab8b7f9fbf0d1_EOF' - - GH_AW_PROMPT_d8bab8b7f9fbf0d1_EOF - cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_d8bab8b7f9fbf0d1_EOF' - - 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` → `__GH_AW_GITHUB_REPOSITORY__` (cwd) [shallow clone, fetch-depth=1] - - **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). - - - GH_AW_PROMPT_d8bab8b7f9fbf0d1_EOF - cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_d8bab8b7f9fbf0d1_EOF' - - {{#runtime-import .github/workflows/auto-api-docs-reviewer.md}} - GH_AW_PROMPT_d8bab8b7f9fbf0d1_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" - 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_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_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' - 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_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_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 - } - }); - - 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/github_rate_limits.jsonl - /tmp/gh-aw/base - if-no-files-found: ignore - retention-days: 1 - - agent: - needs: activation - runs-on: ubuntu-latest - permissions: - contents: 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: autoapidocsreviewer - 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@b8068426813005612b960b5ab0b8bd2c27142323 # v0.71.5 - 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: "Auto API Docs Reviewer" - GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/auto-api-docs-reviewer.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: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - fetch-depth: 1 - - 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.40 - - 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" - - env: - REVIEW_SCOPE: ${{ inputs.scope || 'group:text' }} - name: Record review scope - run: "printf '%s' \"${REVIEW_SCOPE}\" > review-scope.txt\necho \"Review scope: $(cat review-scope.txt)\"\n" - - env: - SKIASHARP_BRANCH: ${{ inputs.skiasharp_branch || 'main' }} - name: Clone SkiaSharp (shallow, with submodules) - run: "git clone --depth 1 --branch \"$SKIASHARP_BRANCH\" \\\n --recurse-submodules --shallow-submodules \\\n https://github.com/mono/SkiaSharp.git skiasharp\nmkdir -p skiasharp/docs\nln -sfn \"$(pwd)/SkiaSharpAPI\" skiasharp/docs/SkiaSharpAPI\ncd skiasharp && dotnet tool restore\n" - - continue-on-error: true - name: Bootstrap SkiaSharp binding for snippet checks - run: |- - cd skiasharp - dotnet cake --target=externals-download - dotnet build binding/SkiaSharp/SkiaSharp.csproj -c Release - - - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.40@sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40@sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280 ghcr.io/github/gh-aw-firewall/squid:0.25.40@sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51 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_c0b458da0c2dd9de_EOF' - {"create_pull_request":{"base_branch":"main","draft":false,"max":1,"max_patch_files":100,"max_patch_size":1024,"preserve_branch_name":true,"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"],"recreate_ref":true},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_c0b458da0c2dd9de_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." - }, - "repo_params": {}, - "dynamic_tools": [] - } - 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: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_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_2bf8dc3deb29f470_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" - }, - "guard-policies": { - "allow-only": { - "approval-labels": ${{ steps.parse-guard-vars.outputs.approval_labels }}, - "blocked-users": ${{ steps.parse-guard-vars.outputs.blocked_users }}, - "min-integrity": "none", - "repos": [ - "mono/skiasharp", - "mono/skiasharp-api-docs" - ], - "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:mono/skiasharp", - "private:mono/skiasharp-api-docs" - ] - } - } - } - }, - "gateway": { - "port": $MCP_GATEWAY_PORT, - "domain": "${MCP_GATEWAY_DOMAIN}", - "apiKey": "${MCP_GATEWAY_API_KEY}", - "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" - } - } - GH_AW_MCP_CONFIG_2bf8dc3deb29f470_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: 120 - 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.40/awf-config.schema.json","network":{"allowDomains":["*.githubusercontent.com","*.vsblob.vsassets.io","api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","api.nuget.org","api.snapcraft.io","archive.ubuntu.com","azure.archive.ubuntu.com","azuresearch-usnc.nuget.org","azuresearch-ussc.nuget.org","builds.dotnet.microsoft.com","ci.dot.net","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","dc.services.visualstudio.com","dist.nuget.org","docs.github.com","dot.net","dotnet.microsoft.com","dotnetcli.blob.core.windows.net","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","nuget.org","nuget.pkg.github.com","nugetregistryv2prod.blob.core.windows.net","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","oneocsp.microsoft.com","packagecloud.io","packages.cloud.google.com","packages.microsoft.com","pkgs.dev.azure.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","www.microsoft.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.40,squid=sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51,agent=sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504,api-proxy=sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280,cli-proxy=sha256:3e7152911d4b4b7b97beef9d3d7d924ff7902227e86001ef3838fb728d5d514c"}}' > "${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.71.5 - 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: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_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: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' - 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,*.vsblob.vsassets.io,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,ci.dot.net,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,dc.services.visualstudio.com,dist.nuget.org,docs.github.com,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,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,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,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,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pkgs.dev.azure.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,www.microsoft.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+r /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: Format docs - run: cd skiasharp && dotnet cake --target=docs-format-docs - - - 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 - - conclusion: - needs: - - activation - - agent - - detection - - 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-auto-api-docs-reviewer" - 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@b8068426813005612b960b5ab0b8bd2c27142323 # v0.71.5 - 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: "Auto API Docs Reviewer" - GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/auto-api-docs-reviewer.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: 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: "Auto API Docs Reviewer" - 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: ${{ 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/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: "Auto API Docs Reviewer" - 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: ${{ 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/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: "Auto API Docs Reviewer" - with: - github-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/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: "Auto API Docs Reviewer" - with: - github-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/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: "Auto API Docs Reviewer" - 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: "auto-api-docs-reviewer" - 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_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: "120" - with: - github-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/handle_agent_failure.cjs'); - await main(); - - 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@b8068426813005612b960b5ab0b8bd2c27142323 # v0.71.5 - 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: "Auto API Docs Reviewer" - GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/auto-api-docs-reviewer.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.40@sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40@sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280 ghcr.io/github/gh-aw-firewall/squid:0.25.40@sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51 - - 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: "Auto API Docs Reviewer" - WORKFLOW_DESCRIPTION: "On-demand API documentation review — audits EXISTING mdoc XML for a scope (default the text/font slice), fixes CRITICAL + obsolete-example findings by editing the XML directly, and opens a PR. Doubles as the CI exercise of per-sub-agent model routing." - 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.40 - - 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.40/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.40,squid=sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51,agent=sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504,api-proxy=sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280,cli-proxy=sha256:3e7152911d4b4b7b97beef9d3d7d924ff7902227e86001ef3838fb728d5d514c"}}' > "${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.71.5 - 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); - } - } - - 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 }}/auto-api-docs-reviewer" - 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: "auto-api-docs-reviewer" - GH_AW_WORKFLOW_NAME: "Auto API Docs Reviewer" - outputs: - 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@b8068426813005612b960b5ab0b8bd2c27142323 # v0.71.5 - 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: "Auto API Docs Reviewer" - GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/auto-api-docs-reviewer.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: 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: - ref: main - token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_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: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} - GIT_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.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:${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: 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,*.vsblob.vsassets.io,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,ci.dot.net,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,dc.services.visualstudio.com,dist.nuget.org,docs.github.com,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,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,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,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,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pkgs.dev.azure.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,www.microsoft.com" - GITHUB_SERVER_URL: ${{ github.server_url }} - GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_pull_request\":{\"base_branch\":\"main\",\"draft\":false,\"max\":1,\"max_patch_files\":100,\"max_patch_size\":1024,\"preserve_branch_name\":true,\"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\"],\"recreate_ref\":true},\"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 }} - with: - github-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/safe_output_handler_manager.cjs'); - await main(); - - 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 - diff --git a/.github/workflows/auto-api-docs-reviewer.md b/.github/workflows/auto-api-docs-reviewer.md deleted file mode 100644 index b63885a..0000000 --- a/.github/workflows/auto-api-docs-reviewer.md +++ /dev/null @@ -1,251 +0,0 @@ ---- -description: "On-demand API documentation review — audits EXISTING mdoc XML for a scope (default the text/font slice), fixes CRITICAL + obsolete-example findings by editing the XML directly, and opens a PR. Doubles as the CI exercise of per-sub-agent model routing." - -# -- Triggers ---------------------------------------------------------- -# Dispatch-only: this is the periodic/on-demand REVIEW dual of the daily -# auto-api-docs-writer (which only fills `To be added.` placeholders). Review -# operates on already-filled docs, so there is no stub-regeneration job here. -on: - workflow_dispatch: - inputs: - skiasharp_branch: - description: "SkiaSharp branch to use for the skill, scripts, source x-ref, and binding build" - required: false - default: "main" - type: string - scope: - description: "Review scope selector (docs-tool.ps1 grammar): group:text, type:SKFont, ns:HarfBuzzSharp, all, changed, match:font" - required: false - default: "group:text" - type: string - -# -- Checkout ---------------------------------------------------------- -# Primary: this docs repo (its committed SkiaSharpAPI/*.xml is what we review). -# SkiaSharp is cloned in pre-agent-steps for the skill + C# source + binding. -# fetch-depth: 1 is sufficient — `validate` compares each edited file against the -# checked-out HEAD (the working-tree fallback in docs-tool.ps1), not origin/main. -checkout: - - fetch-depth: 1 -timeout-minutes: 120 -concurrency: - group: auto-api-docs-reviewer - cancel-in-progress: true - -# -- Agent tools ------------------------------------------------------- -tools: - github: - toolsets: [repos] - allowed-repos: ["mono/skiasharp", "mono/skiasharp-api-docs"] - min-integrity: none - bash: ["*"] - edit: - -# -- Network allowlist ------------------------------------------------- -network: - allowed: - - defaults - - github - - dotnet - -# -- Permissions ------------------------------------------------------- -permissions: - contents: read - -# -- Safe outputs ------------------------------------------------------ -safe-outputs: - create-pull-request: - draft: false - base-branch: main - preserve-branch-name: true - recreate-ref: true - -# -- Pre-agent steps (host) ------------------------------------------- -pre-agent-steps: - - name: Record review scope - env: - REVIEW_SCOPE: ${{ inputs.scope || 'group:text' }} - run: | - printf '%s' "${REVIEW_SCOPE}" > review-scope.txt - echo "Review scope: $(cat review-scope.txt)" - - - name: Clone SkiaSharp (shallow, with submodules) - env: - SKIASHARP_BRANCH: ${{ inputs.skiasharp_branch || 'main' }} - run: | - git clone --depth 1 --branch "$SKIASHARP_BRANCH" \ - --recurse-submodules --shallow-submodules \ - https://github.com/mono/SkiaSharp.git skiasharp - mkdir -p skiasharp/docs - ln -sfn "$(pwd)/SkiaSharpAPI" skiasharp/docs/SkiaSharpAPI - cd skiasharp && dotnet tool restore - - # Build the managed binding so reviewer-examples can compile-check snippets - # against a real SkiaSharp.dll. C#-only — externals-download fetches prebuilt - # natives (no native changes here). Non-fatal: a build hiccup must not sink the - # review, since the reviewer also verifies examples by reading source. - - name: Bootstrap SkiaSharp binding for snippet checks - continue-on-error: true - run: | - cd skiasharp - dotnet cake --target=externals-download - dotnet build binding/SkiaSharp/SkiaSharp.csproj -c Release - -# -- Post-agent steps (host) ------------------------------------------ -# Format docs AFTER the agent edits the XML in place. Runs on host outside the -# sandbox so it has full access to the SkiaSharp cake scripts. -post-steps: - - name: Format docs - run: cd skiasharp && dotnet cake --target=docs-format-docs ---- - -# Auto API Docs Reviewer - -You are the **orchestrator** for the `review` workflow — the dual of the writer. The writer fills blanks; -**you audit and improve docs that are already filled** for a scope, fix the high-severity issues, and open a -PR. Read these first, then drive the phases below: - -- `skiasharp/.agents/skills/api-docs/SKILL.md` — the router. -- `skiasharp/.agents/skills/api-docs/workflows/review.md` — the review pipeline **and the per-role model table**. -- `skiasharp/.agents/skills/api-docs/workflows/scope-resolution.md` — how the scope selector resolves to files. -- `skiasharp/.agents/skills/api-docs/workflows/validation.md` — the post-edit gates. - -The docs repo is the **primary checkout**; its committed `SkiaSharpAPI/*.xml` is what you review. There is -**no extract/merge JSON step** — you edit the mdoc XML directly; safety comes from the structural validator. - -## Why this run matters (it is also an eval) - -This workflow is the first real CI exercise of **per-sub-agent model routing**. You MUST launch every -sub-agent via the `task` tool with an explicit `model`, and you MUST report — in stdout and in the PR body — -which model you requested for each role and whether the sandbox honored it. See "Routing report" below. - -## Model routing - -You run on the default `engine.model` (cheap orchestrator). You do **not** review or write docs yourself. -Launch every sub-agent **via the `task` tool with an explicit `model`**, reading the per-role value from the -table in `workflows/review.md` and each agent file's `Model:` header: - -| Sub-agent | Model | -|---|---| -| reviewer-factual | `gpt-5.5` (eval bake-off winner) | -| reviewer-examples | `claude-opus-4.6` | -| reviewer-quality | `claude-sonnet-4.6` | -| review-synthesizer | `claude-sonnet-4.6` | -| writer (fix step) | `claude-opus-4.6` | - -If the sandbox does **not** honor per-sub-agent `model` (it errors, or forces the parent model), proceed on -`engine.model` for all roles and record that in the Routing report and PR body — **do not abort**. - -## Scope environment - -`docs-tool.ps1` lives in the SkiaSharp clone, so by default it looks for docs under `skiasharp/docs`. Here the -**docs repo is the primary checkout**, so point the tool at it by exporting these on every `docs-tool.ps1` -call (they make `resolve-scope`, `lint`, and `validate` use the docs repo for git baselines/diffs while source -lookups still use the SkiaSharp clone): - -```bash -DOCS_GIT_ROOT="$GITHUB_WORKSPACE" DOCS_DIR="$GITHUB_WORKSPACE/SkiaSharpAPI" -``` - -The review scope selector is in `review-scope.txt` at the workspace root (default `group:text`). - -## Execution order - -1. **Resolve scope.** Read the selector and resolve it to an explicit file list (fuzzy selectors need - `-Confirm:$false` in CI). The text/font slice is ~16 files — one batch; for larger scopes, shard into - ~25–40-file batches. - ```bash - SCOPE="$(cat review-scope.txt)" - cd skiasharp && DOCS_GIT_ROOT="$GITHUB_WORKSPACE" DOCS_DIR="$GITHUB_WORKSPACE/SkiaSharpAPI" \ - pwsh .agents/skills/api-docs/scripts/docs-tool.ps1 resolve-scope "$SCOPE" -Confirm:$false && cd .. - ``` - -2. **Lint (deterministic, no model).** Run the linter over the resolved files to catch objective defects. - ```bash - cd skiasharp && DOCS_GIT_ROOT="$GITHUB_WORKSPACE" DOCS_DIR="$GITHUB_WORKSPACE/SkiaSharpAPI" \ - pwsh .agents/skills/api-docs/scripts/docs-tool.ps1 lint "$SCOPE" && cd .. - ``` - -3. **Review (three reviewers in parallel).** Launch `reviewer-factual`, `reviewer-examples`, and - `reviewer-quality` via the `task` tool, each with its assigned model, on the resolved file list. They - **report only** (one `SEVERITY | class | file | docId | message` line each). Then feed the linter output + - all three reviewers' findings to `review-synthesizer` to dedupe/normalize. - -4. **Fix (gated, this run).** Edit the XML directly to resolve, in priority order: (a) all **CRITICAL** - findings, (b) **obsolete-in-example** findings (the text/font slice has legacy `paint.TextSize` / - `TextAlign` / `DrawText(string,x,y,paint)` examples — migrate them to `SKFont`), (c) where a central type is - example-poor (e.g. `SKFont`, `SKTypeface`, `SKPaint`), add one correct, compiling example, porting the - `SKCanvas`/`SKShader` quality bar. Launch the **writer** sub-agent for the edits. **Budget:** if you pass - ~60 minutes, stop fixing, validate what you have, and open the PR — a smaller validated PR beats none. - -5. **Validate (MUST pass before the PR).** This is the gate that makes direct editing safe: - ```bash - cd skiasharp && DOCS_GIT_ROOT="$GITHUB_WORKSPACE" DOCS_DIR="$GITHUB_WORKSPACE/SkiaSharpAPI" \ - pwsh .agents/skills/api-docs/scripts/docs-tool.ps1 validate "$SCOPE" && cd .. - ``` - It asserts each changed file is well-formed, has unchanged signature counts, and changed **only** inside - ``. Do **not** run `docs-format-docs` — formatting runs automatically as a post-step. - -6. **Routing report (ALWAYS).** Print to stdout a block delimited exactly like this, filled in from what the - `task` tool actually did, so the run is auditable even if no fixes were made: - ``` - === ROUTING REPORT === - reviewer-factual | requested: gpt-5.5 | honored: | note: ... - reviewer-examples | requested: claude-opus-4.6 | honored: | note: ... - reviewer-quality | requested: claude-sonnet-4.6| honored: | note: ... - review-synthesizer | requested: claude-sonnet-4.6| honored: | note: ... - writer | requested: claude-opus-4.6 | honored: | note: ... - === END ROUTING REPORT === - ``` - Base "honored" on observable evidence: whether the `task` call accepted the `model` parameter, any - model-not-supported error the sandbox surfaced, and any self-reported model from the sub-agent. If you had - to fall back to a single model for all roles, say so explicitly. - -7. **Commit and PR.** If step 4 produced edits: - ```bash - git checkout -b automation/review-api-docs - git add SkiaSharpAPI/ - git commit -m "Review and improve API documentation (scope: )" - ``` - Then use the `create_pull_request` tool: - - Branch: `automation/review-api-docs` - - Title: `Review and improve API documentation ()` - - Body: include (a) the scope reviewed and file count, (b) the **Routing report** block verbatim, (c) a - short **Findings summary** (counts by severity + the synthesizer's machine `FINDING |` block), and (d) - what you fixed vs deferred. - - If step 4 produced **no** edits (nothing CRITICAL/obsolete to fix), call the `noop` tool — but still print - the Routing report and Findings summary to stdout first so the eval is observable in the run logs. - -## Critical rules - -- **Edit the mdoc XML directly.** Touch only `` content — never `MemberSignature`/`TypeSignature`, - attributes, or generated files (`index.xml`, `ns-*.xml`, `_filter.xml`, `FrameworksIndex/`). The structural - validator enforces this; a failure means you edited outside ``. -- **Step 5 (validate) MUST pass before the PR.** If you skip it, a malformed or surface-changing edit can ship. -- **Sub-agents must NOT spawn their own sub-agents.** Each agent does all its work directly — nested sub-agents - hit the depth limit and time out. -- **Do NOT run `docs-format-docs`** — formatting runs automatically as a post-step. -- **Every code example you add or change must compile** against the real `SkiaSharp.dll` (bootstrapped in a - pre-step) and use **no obsolete members** (see `references/obsolete-api-map.md`). A non-compiling example is - worse than none. -- **NEVER end a turn without a tool call while waiting for agents.** When you launch a background agent, you - MUST call `read_agent` with `wait: true` in the SAME response. Outputting "waiting" and ending the turn - terminates the session and loses all work. - - **Single agent:** `task(background)` + `read_agent(id, wait=true)` in the same response. - - **Multiple agents:** launch all, then `read_agent` the first with `wait: true`; when it returns, read the - next, and so on. Keep an active `read_agent` call at all times until all agents complete. - - **FORBIDDEN:** launching agents → "Waiting for them to complete" → ending the turn. This KILLS the session. -- **COMPLETION GATE:** Your session is NOT complete until you have called `create_pull_request` or `noop`, and - you have printed the Routing report. If you think you're done but did neither, retrace your steps and finish. - -## Path differences from SKILL.md - -Because this workflow runs from the docs repo (not SkiaSharp), paths differ: - -| SKILL.md reference | Actual path in this workflow | -|---|---| -| `docs/SkiaSharpAPI/` | `SkiaSharpAPI/` (repo root) | -| `.agents/skills/api-docs/` | `skiasharp/.agents/skills/api-docs/` | -| `binding/SkiaSharp/` | `skiasharp/binding/SkiaSharp/` | -| `binding/HarfBuzzSharp/` | `skiasharp/binding/HarfBuzzSharp/` | -| `samples/Gallery/Shared/Samples/` | `skiasharp/samples/Gallery/Shared/Samples/` | diff --git a/.github/workflows/auto-api-docs-writer.lock.yml b/.github/workflows/auto-api-docs-writer.lock.yml index 9d32b96..9d2888d 100644 --- a/.github/workflows/auto-api-docs-writer.lock.yml +++ b/.github/workflows/auto-api-docs-writer.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"cbace7d3317f0dbef709f125cdc6f4107a1b49b73f1778d113159121fd09e1d9","compiler_version":"v0.71.5","strict":true,"agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"e95a7b69b11a45d4c6a7f32bbd5cf94197ee21188e39764de11b9bfe11aba549","compiler_version":"v0.71.5","strict":true,"agent_id":"copilot"} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache","sha":"0057852bfaa89a56745cba8c7296529d2fc39830","version":"v4"},{"repo":"actions/checkout","sha":"34e114876b0b11c390a56381ad16ebd13914f8d5","version":"v4"},{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/download-artifact","sha":"d3f86a106a0bac45b974a628896c90dbdf5c8093","version":"v4"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-dotnet","sha":"67a3573c9a986a3f9c594539f4ab511d57bb3ce9","version":"v4"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"actions/upload-artifact","sha":"ea165f8d65b6e75b540449e92b4886f43607fa02","version":"v4"},{"repo":"github/gh-aw-actions/setup","sha":"b8068426813005612b960b5ab0b8bd2c27142323","version":"v0.71.5"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.40","digest":"sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.40@sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40","digest":"sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40@sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.40","digest":"sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.40@sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51"},{"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"}]} # ___ _ _ # / _ \ | | (_) @@ -22,7 +22,7 @@ # # For more information: https://github.github.com/gh-aw/introduction/overview/ # -# Daily API documentation pipeline — regenerates XML stubs from CI NuGets, then AI fills 'To be added.' placeholders by editing the mdoc XML directly. +# Daily API documentation pipeline — regenerates XML stubs from CI NuGets, then AI (1) fills 'To be added.' placeholders [add] and (2) reviews & improves a scope of existing docs [review], editing the mdoc XML directly. # # Secrets used: # - COPILOT_GITHUB_TOKEN @@ -201,23 +201,23 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_c2191c4c1abba5bd_EOF' + cat << 'GH_AW_PROMPT_c6551dc4e2d385fc_EOF' - GH_AW_PROMPT_c2191c4c1abba5bd_EOF + GH_AW_PROMPT_c6551dc4e2d385fc_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_c2191c4c1abba5bd_EOF' + cat << 'GH_AW_PROMPT_c6551dc4e2d385fc_EOF' Tools: create_pull_request, missing_tool, missing_data, noop - GH_AW_PROMPT_c2191c4c1abba5bd_EOF + GH_AW_PROMPT_c6551dc4e2d385fc_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" - cat << 'GH_AW_PROMPT_c2191c4c1abba5bd_EOF' + cat << 'GH_AW_PROMPT_c6551dc4e2d385fc_EOF' - GH_AW_PROMPT_c2191c4c1abba5bd_EOF + GH_AW_PROMPT_c6551dc4e2d385fc_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_c2191c4c1abba5bd_EOF' + cat << 'GH_AW_PROMPT_c6551dc4e2d385fc_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -249,12 +249,12 @@ jobs: - **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). - GH_AW_PROMPT_c2191c4c1abba5bd_EOF + GH_AW_PROMPT_c6551dc4e2d385fc_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_c2191c4c1abba5bd_EOF' + cat << 'GH_AW_PROMPT_c6551dc4e2d385fc_EOF' {{#runtime-import .github/workflows/auto-api-docs-writer.md}} - GH_AW_PROMPT_c2191c4c1abba5bd_EOF + GH_AW_PROMPT_c6551dc4e2d385fc_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -441,6 +441,8 @@ jobs: with: name: docs-regenerated path: SkiaSharpAPI/ + - name: Record review scope + run: "printf '%s' \"group:text\" > review-scope.txt\necho \"Review scope (existing docs): $(cat review-scope.txt)\"\n" - env: SKIASHARP_BRANCH: ${{ inputs.skiasharp_branch || 'main' }} name: Clone SkiaSharp (shallow, with submodules) @@ -459,9 +461,9 @@ jobs: 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_95bfbea592eaca2e_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_8eea27ce9ee671cf_EOF' {"create_pull_request":{"base_branch":"main","draft":false,"max":1,"max_patch_files":100,"max_patch_size":1024,"preserve_branch_name":true,"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"],"recreate_ref":true},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_95bfbea592eaca2e_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_8eea27ce9ee671cf_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -667,7 +669,7 @@ jobs: 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_f0ffca7316016fa8_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_f2954ea83d249842_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -715,7 +717,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_f0ffca7316016fa8_EOF + GH_AW_MCP_CONFIG_f2954ea83d249842_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -1175,7 +1177,7 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: WORKFLOW_NAME: "Auto API Docs Writer" - WORKFLOW_DESCRIPTION: "Daily API documentation pipeline — regenerates XML stubs from CI NuGets, then AI fills 'To be added.' placeholders by editing the mdoc XML directly." + WORKFLOW_DESCRIPTION: "Daily API documentation pipeline — regenerates XML stubs from CI NuGets, then AI (1) fills 'To be added.' placeholders [add] and (2) reviews & improves a scope of existing docs [review], editing the mdoc XML directly." HAS_PATCH: ${{ needs.agent.outputs.has_patch }} with: script: | diff --git a/.github/workflows/auto-api-docs-writer.md b/.github/workflows/auto-api-docs-writer.md index 0355fa8..9e701ef 100644 --- a/.github/workflows/auto-api-docs-writer.md +++ b/.github/workflows/auto-api-docs-writer.md @@ -1,5 +1,5 @@ --- -description: "Daily API documentation pipeline — regenerates XML stubs from CI NuGets, then AI fills 'To be added.' placeholders by editing the mdoc XML directly." +description: "Daily API documentation pipeline — regenerates XML stubs from CI NuGets, then AI (1) fills 'To be added.' placeholders [add] and (2) reviews & improves a scope of existing docs [review], editing the mdoc XML directly." # -- Triggers ---------------------------------------------------------- on: @@ -123,6 +123,16 @@ pre-agent-steps: name: docs-regenerated path: SkiaSharpAPI/ + # The review half of the pipeline audits a scope of EXISTING docs (not just the + # newly-filled placeholders). The scope is baked here rather than as a dispatch + # input so the workflow stays dispatchable on a feature branch (workflow_dispatch + # validates inputs against the default branch). For the daily common path this is + # the high-value text/font slice; change to `changed` to review just-touched docs. + - name: Record review scope + run: | + printf '%s' "group:text" > review-scope.txt + echo "Review scope (existing docs): $(cat review-scope.txt)" + - name: Clone SkiaSharp (shallow, with submodules) env: SKIASHARP_BRANCH: ${{ inputs.skiasharp_branch || 'main' }} @@ -155,11 +165,14 @@ post-steps: # Auto API Docs Writer -You are the **orchestrator** for the `add` workflow. Read these first, then drive the phases below: +You are the **orchestrator** for the unified `add` + `review` pipeline. You run **two passes** in one job: +**(A) Add** — fill `To be added.` placeholders on newly-regenerated stubs; **(R) Review** — audit and improve +a scope of **existing** docs. Read these first, then drive the phases below: - `skiasharp/.agents/skills/api-docs/SKILL.md` — the router. -- `skiasharp/.agents/skills/api-docs/workflows/add.md` — the direct-XML add pipeline you are running. -- `skiasharp/.agents/skills/api-docs/workflows/review.md` — the review pass **and the per-role model table**. +- `skiasharp/.agents/skills/api-docs/workflows/add.md` — the direct-XML add pipeline (pass A). +- `skiasharp/.agents/skills/api-docs/workflows/review.md` — the review pipeline (pass R) **and the per-role model table**. +- `skiasharp/.agents/skills/api-docs/workflows/scope-resolution.md` — how a scope selector resolves to files. - `skiasharp/.agents/skills/api-docs/workflows/validation.md` — the post-edit gates. The stub regeneration (mdoc) already ran as a pre-step, so the placeholder `*.xml` files are present in @@ -175,6 +188,10 @@ factual → `gpt-5.5` per the eval bake-off; quality + synthesizer → `claude-s not honor per-sub-agent `model` (the parent overrides it), proceed on `engine.model` for all roles and note it in the PR body — do not abort. +**This run is also a routing eval.** It is the first CI exercise of per-sub-agent routing, so you MUST emit a +**Routing report** (see the Execution order) recording, per role, the model you requested and whether the +`task` tool honored it — to stdout always, and in the PR body when you open one. + ## Scope environment `docs-tool.ps1` lives in the SkiaSharp clone, so by default it would look for docs under @@ -189,61 +206,118 @@ DOCS_GIT_ROOT="$GITHUB_WORKSPACE" DOCS_DIR="$GITHUB_WORKSPACE/SkiaSharpAPI" ## Execution order -1. **Discover (lightweight).** Resolve the placeholder files into an explicit list and shard it into +### Pass A — Add (fill placeholders) + +A1. **Discover (lightweight).** Resolve the placeholder files into an explicit list and shard it into ~25–40-file batches. Do **not** pre-read source or XML — the writer does its own discovery. ```bash cd skiasharp && DOCS_GIT_ROOT="$GITHUB_WORKSPACE" DOCS_DIR="$GITHUB_WORKSPACE/SkiaSharpAPI" \ pwsh .agents/skills/api-docs/scripts/docs-tool.ps1 resolve-scope new && cd .. ``` -2. **Write (per batch).** For each batch, launch the **writer** sub-agent (`agents/writer.md`, model from its +A2. **Write (per batch).** For each batch, launch the **writer** sub-agent (`agents/writer.md`, model from its header) with the resolved file list. It reads the C# source, fills only the empty/`To be added.` fields, and edits the XML in place. A type it cannot document with certainty keeps its placeholder (a `DEFERRED` line) so the next run re-detects it. -3. **Review (per batch).** Run the deterministic linter, then launch the **three** reviewers in parallel +A3. **Review the written batch.** Run the deterministic linter, then launch the **three** reviewers in parallel (`reviewer-factual`, `reviewer-examples`, `reviewer-quality`), each on the batch's files with its assigned model. Feed all findings to `review-synthesizer`. + +A4. **Fix CRITICAL findings** by editing the XML directly. Skip MINOR/style for the automated run. + +> If `resolve-scope new` returns **no** placeholder files (the common case once docs are filled), pass A is a +> no-op — skip straight to pass R. + +### Pass R — Review existing docs (a scope, not just placeholders) + +The review scope is in `review-scope.txt` at the workspace root (a selector like `group:text`). This audits +docs that are **already filled**, which is where freshness/accuracy/example problems live. + +R1. **Resolve the review scope** (fuzzy selectors need `-Confirm:$false` in CI): ```bash + SCOPE="$(cat review-scope.txt)" cd skiasharp && DOCS_GIT_ROOT="$GITHUB_WORKSPACE" DOCS_DIR="$GITHUB_WORKSPACE/SkiaSharpAPI" \ - pwsh .agents/skills/api-docs/scripts/docs-tool.ps1 lint new && cd .. + pwsh .agents/skills/api-docs/scripts/docs-tool.ps1 resolve-scope "$SCOPE" -Confirm:$false && cd .. ``` + Shard >40 files into batches; the text/font slice is ~16 files (one batch). -4. **Fix CRITICAL findings** by editing the XML directly. Skip MINOR/style for the automated run. +R2. **Lint** the scope (deterministic, no model): + ```bash + cd skiasharp && DOCS_GIT_ROOT="$GITHUB_WORKSPACE" DOCS_DIR="$GITHUB_WORKSPACE/SkiaSharpAPI" \ + pwsh .agents/skills/api-docs/scripts/docs-tool.ps1 lint "$SCOPE" && cd .. + ``` + +R3. **Review (three reviewers in parallel)** on the resolved file list, each via the `task` tool with its + assigned model; they report only. Feed the linter output + all three reviewers' findings to + `review-synthesizer`. + +R4. **Fix (gated)** by editing the XML directly, in priority order: (a) all **CRITICAL** findings, (b) + **obsolete-in-example** findings (the text/font slice has legacy `paint.TextSize` / `TextAlign` / + `DrawText(string,x,y,paint)` examples — migrate them to `SKFont`), (c) where a central type is example-poor + (`SKFont`, `SKTypeface`, `SKPaint`), add one correct, **compiling** example, porting the `SKCanvas`/`SKShader` + quality bar. Use the **writer** sub-agent for the edits. **Budget:** if you pass ~60 minutes total, stop + fixing, validate what you have, and open the PR — a smaller validated PR beats none. -5. **Validate (replaces merge).** This is the gate that makes direct editing safe — it must pass before the PR: +### Finalize + +V. **Validate (replaces merge).** This gate makes direct editing safe — it must pass before the PR, and it + covers **all** edits from both passes: ```bash cd skiasharp && DOCS_GIT_ROOT="$GITHUB_WORKSPACE" DOCS_DIR="$GITHUB_WORKSPACE/SkiaSharpAPI" \ pwsh .agents/skills/api-docs/scripts/docs-tool.ps1 validate new && cd .. ``` - It asserts each changed file is well-formed, has unchanged signature counts, and changed **only** inside - ``. Do **not** run `docs-format-docs` — formatting runs automatically as a post-step. + `new` resolves to every changed `.xml` (placeholders + reviewed files) via the working-tree diff. It asserts + each is well-formed, has unchanged signature counts, and changed **only** inside ``. Do **not** run + `docs-format-docs` — formatting runs automatically as a post-step. + +ROUTING. **Routing report (ALWAYS).** Print to stdout a block delimited exactly like this, filled in from what + the `task` tool actually did, so the routing eval is auditable even if no edits were made: + ``` + === ROUTING REPORT === + reviewer-factual | requested: gpt-5.5 | honored: | note: ... + reviewer-examples | requested: claude-opus-4.6 | honored: | note: ... + reviewer-quality | requested: claude-sonnet-4.6| honored: | note: ... + review-synthesizer | requested: claude-sonnet-4.6| honored: | note: ... + writer | requested: claude-opus-4.6 | honored: | note: ... + === END ROUTING REPORT === + ``` + Base "honored" on observable evidence: whether the `task` call accepted the `model` parameter, any + model-not-supported error the sandbox surfaced, and any self-reported model from the sub-agent. If you fell + back to a single model for all roles, say so explicitly. -6. **Commit and PR.** Commit the XML changes and open a pull request: +C. **Commit and PR.** If any pass produced edits: ```bash git checkout -b automation/write-api-docs git add SkiaSharpAPI/ - git commit -m "Fill API documentation placeholders" + git commit -m "Fill and review API documentation" ``` Then use the `create_pull_request` tool: - Branch: `automation/write-api-docs` - - Title: `Fill API documentation placeholders` - - Body: `Automated AI-generated documentation for XML API docs with 'To be added.' placeholders.` + - Title: `Fill and review API documentation` + - Body: include (a) what pass A filled (file count) and what pass R reviewed (scope + file count), (b) the + **Routing report** block verbatim, (c) a short **Findings summary** (counts by severity + the synthesizer's + machine `FINDING |` block), and (d) what you fixed vs deferred. -If there are no documentation changes after validation, call the `noop` tool instead. +If there are no documentation changes after validation, call the `noop` tool instead — but still print the +Routing report and Findings summary to stdout first so the routing eval is observable in the run logs. ## Critical rules - **Edit the mdoc XML directly.** There is no JSON round-trip. Touch only `` content — never `MemberSignature`/`TypeSignature`, attributes, or generated files (`index.xml`, `ns-*.xml`, `_filter.xml`, `FrameworksIndex/`). The structural validator enforces this; a failure means you edited outside ``. -- **Step 5 (validate) MUST pass before the PR.** If you skip it, a malformed or surface-changing edit can ship. +- **The validate gate (step V) MUST pass before the PR.** If you skip it, a malformed or surface-changing edit can ship. - **Sub-agents must NOT spawn their own sub-agents.** Each agent does all its work directly — nested sub-agents hit the depth limit and time out. - **Do NOT run `docs-format-docs`** — formatting runs automatically as a post-step. -- **Budget awareness:** Fix CRITICAL findings and proceed to validate + PR promptly. Do not re-run reviewers - unless necessary. **If you're past 10 minutes and haven't reached step 5, skip review (step 3) entirely and - go straight to validate + PR.** A validated PR without review beats no PR — but never skip step 5. +- **Every code example you add or change must compile** against the real `SkiaSharp.dll` (bootstrapped in a + pre-step) and use **no obsolete members** (see `references/obsolete-api-map.md`). A non-compiling example is + worse than none. +- **Budget awareness:** Prioritize reaching validate + PR. Pass A (add) is usually a no-op now, so spend the + budget on pass R. Do not re-run reviewers unnecessarily. **If you pass ~60 minutes total and haven't reached + step V, stop fixing, validate what you have, and open the PR.** A smaller validated PR beats none — but never + skip step V, and always print the Routing report. - **NEVER end a turn without a tool call while waiting for agents.** When you launch a background agent, you MUST call `read_agent` with `wait: true` in the SAME response. Outputting "waiting" and ending the turn terminates the session and loses all work. @@ -251,8 +325,8 @@ If there are no documentation changes after validation, call the `noop` tool ins - **Multiple agents:** launch all, then `read_agent` the first with `wait: true`; when it returns, read the next, and so on. Keep an active `read_agent` call at all times until all agents complete. - **FORBIDDEN:** launching agents → "Waiting for them to complete" → ending the turn. This KILLS the session. -- **COMPLETION GATE:** Your session is NOT complete until you have called `create_pull_request` or `noop`. If - you think you're done but called neither, retrace your steps and finish the remaining phases. +- **COMPLETION GATE:** Your session is NOT complete until you have printed the **Routing report** AND called + `create_pull_request` or `noop`. If you think you're done but did neither, retrace your steps and finish. ## Path differences from SKILL.md From 3be9831eed87a024dcf57b305caee9fb3c69abc9 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Thu, 25 Jun 2026 23:59:13 +0200 Subject: [PATCH 5/7] Restore orchestration discipline: orchestrator does terminal fix+PR itself The first CI pilot run completed green but produced NO PR. Root cause: the orchestrator delegated the terminal fix step to a background "fixer" sub-agent, then ended its turn before that agent (and the PR) completed -- exactly the session-killing pattern the older workflow had guardrails against. Separately, per-sub-agent model routing turned out to be cosmetic in the gh-aw sandbox: the api-proxy token-usage log showed every call was claude-sonnet-4.6 regardless of the requested per-role model. Fixes: - Pin the run model: explicit engine.model claude-sonnet-4.6 (drops the GH_AW_MODEL_AGENT_COPILOT override; orchestrator + every sub-agent run on it). - Drop cosmetic per-role routing and the routing-report requirement; document per-role routing as a local-only skill feature. - Terminal fixes are now the orchestrator's own FOREGROUND work (A4/R4); the only sub-agents launched are the pass-A writer and the reviewers. No terminal background agent. Synthesis is the orchestrator's job (no synthesizer agent). - Restore the tight escape hatch: once reviewers report, timebox fixing to ~10 min, then validate + open the PR. - Add an explicit "No terminal background agent" Critical rule and simplify the completion gate (create_pull_request/noop is the orchestrator's own job). Recompiled auto-api-docs-writer.lock.yml. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../workflows/auto-api-docs-writer.lock.yml | 38 +++--- .github/workflows/auto-api-docs-writer.md | 108 +++++++++--------- 2 files changed, 76 insertions(+), 70 deletions(-) diff --git a/.github/workflows/auto-api-docs-writer.lock.yml b/.github/workflows/auto-api-docs-writer.lock.yml index 9d2888d..097b906 100644 --- a/.github/workflows/auto-api-docs-writer.lock.yml +++ b/.github/workflows/auto-api-docs-writer.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"e95a7b69b11a45d4c6a7f32bbd5cf94197ee21188e39764de11b9bfe11aba549","compiler_version":"v0.71.5","strict":true,"agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"a977482f3da156d5a71f706afff5cf60162806e9c08090e6f4f47753bdc5dee7","compiler_version":"v0.71.5","strict":true,"agent_id":"copilot","agent_model":"claude-sonnet-4.6"} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache","sha":"0057852bfaa89a56745cba8c7296529d2fc39830","version":"v4"},{"repo":"actions/checkout","sha":"34e114876b0b11c390a56381ad16ebd13914f8d5","version":"v4"},{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/download-artifact","sha":"d3f86a106a0bac45b974a628896c90dbdf5c8093","version":"v4"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-dotnet","sha":"67a3573c9a986a3f9c594539f4ab511d57bb3ce9","version":"v4"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"actions/upload-artifact","sha":"ea165f8d65b6e75b540449e92b4886f43607fa02","version":"v4"},{"repo":"github/gh-aw-actions/setup","sha":"b8068426813005612b960b5ab0b8bd2c27142323","version":"v0.71.5"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.40","digest":"sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.40@sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40","digest":"sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40@sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.40","digest":"sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.40@sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51"},{"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"}]} # ___ _ _ # / _ \ | | (_) @@ -116,7 +116,7 @@ jobs: 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_MODEL: "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.71.5" @@ -201,23 +201,23 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_c6551dc4e2d385fc_EOF' + cat << 'GH_AW_PROMPT_834eec06139fdaf5_EOF' - GH_AW_PROMPT_c6551dc4e2d385fc_EOF + GH_AW_PROMPT_834eec06139fdaf5_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_c6551dc4e2d385fc_EOF' + cat << 'GH_AW_PROMPT_834eec06139fdaf5_EOF' Tools: create_pull_request, missing_tool, missing_data, noop - GH_AW_PROMPT_c6551dc4e2d385fc_EOF + GH_AW_PROMPT_834eec06139fdaf5_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" - cat << 'GH_AW_PROMPT_c6551dc4e2d385fc_EOF' + cat << 'GH_AW_PROMPT_834eec06139fdaf5_EOF' - GH_AW_PROMPT_c6551dc4e2d385fc_EOF + GH_AW_PROMPT_834eec06139fdaf5_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_c6551dc4e2d385fc_EOF' + cat << 'GH_AW_PROMPT_834eec06139fdaf5_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -249,12 +249,12 @@ jobs: - **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). - GH_AW_PROMPT_c6551dc4e2d385fc_EOF + GH_AW_PROMPT_834eec06139fdaf5_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_c6551dc4e2d385fc_EOF' + cat << 'GH_AW_PROMPT_834eec06139fdaf5_EOF' {{#runtime-import .github/workflows/auto-api-docs-writer.md}} - GH_AW_PROMPT_c6551dc4e2d385fc_EOF + GH_AW_PROMPT_834eec06139fdaf5_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -461,9 +461,9 @@ jobs: 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_8eea27ce9ee671cf_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_5a9750efa4c49447_EOF' {"create_pull_request":{"base_branch":"main","draft":false,"max":1,"max_patch_files":100,"max_patch_size":1024,"preserve_branch_name":true,"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"],"recreate_ref":true},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_8eea27ce9ee671cf_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_5a9750efa4c49447_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -669,7 +669,7 @@ jobs: 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_f2954ea83d249842_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_84a9b7fc8e0ed44c_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -717,7 +717,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_f2954ea83d249842_EOF + GH_AW_MCP_CONFIG_84a9b7fc8e0ed44c_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -757,7 +757,7 @@ jobs: 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' }} + COPILOT_MODEL: 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 @@ -1221,7 +1221,7 @@ jobs: 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' }} + COPILOT_MODEL: claude-sonnet-4.6 GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_VERSION: v0.71.5 @@ -1382,7 +1382,7 @@ jobs: 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_MODEL: "claude-sonnet-4.6" GH_AW_ENGINE_VERSION: "1.0.40" GH_AW_WORKFLOW_ID: "auto-api-docs-writer" GH_AW_WORKFLOW_NAME: "Auto API Docs Writer" diff --git a/.github/workflows/auto-api-docs-writer.md b/.github/workflows/auto-api-docs-writer.md index 9e701ef..11f14e6 100644 --- a/.github/workflows/auto-api-docs-writer.md +++ b/.github/workflows/auto-api-docs-writer.md @@ -87,6 +87,16 @@ concurrency: group: auto-api-docs-writer cancel-in-progress: true +# -- Engine (pin the run model) --------------------------------------- +# Per-role model routing is cosmetic in the gh-aw sandbox: the task tool's +# `model` param is not plumbed through to the actual API call (verified via the +# api-proxy token-usage log — every call was claude-sonnet-4.6 regardless of the +# requested per-agent model). So pin one good model for the whole run — the +# orchestrator and every sub-agent — rather than pretend to route per role. +engine: + id: copilot + model: claude-sonnet-4.6 + # -- Agent tools ------------------------------------------------------- tools: github: @@ -171,7 +181,8 @@ a scope of **existing** docs. Read these first, then drive the phases below: - `skiasharp/.agents/skills/api-docs/SKILL.md` — the router. - `skiasharp/.agents/skills/api-docs/workflows/add.md` — the direct-XML add pipeline (pass A). -- `skiasharp/.agents/skills/api-docs/workflows/review.md` — the review pipeline (pass R) **and the per-role model table**. +- `skiasharp/.agents/skills/api-docs/workflows/review.md` — the review pipeline (pass R). Its per-role model + table is **local-only**; on CI ignore it and run every role on the pinned `engine.model` (see Model routing). - `skiasharp/.agents/skills/api-docs/workflows/scope-resolution.md` — how a scope selector resolves to files. - `skiasharp/.agents/skills/api-docs/workflows/validation.md` — the post-edit gates. @@ -181,16 +192,16 @@ and **edit the mdoc XML directly**; safety comes from the structural validator, ## Model routing -You run on the default `engine.model` (cheap orchestrator). You do **not** write docs yourself. Launch every -sub-agent **via the `task` tool with an explicit `model`**, reading the per-role value from the table in -`workflows/review.md` and each agent file's `Model:` header (writer + examples → `claude-opus-4.6`; -factual → `gpt-5.5` per the eval bake-off; quality + synthesizer → `claude-sonnet-4.6`). If the sandbox does -not honor per-sub-agent `model` (the parent overrides it), proceed on `engine.model` for all roles and note -it in the PR body — do not abort. +The orchestrator **and** every sub-agent run on the single run model (`engine.model`, pinned to +`claude-sonnet-4.6`). Launch sub-agents via the `task` tool **without** a per-role `model` parameter — they +inherit the run model. You delegate **bulk** writing (pass-A placeholder fill) and all reviewing to +sub-agents, but you perform the **terminal** fixes, validation, commit, and PR **yourself** (see Critical +rules). -**This run is also a routing eval.** It is the first CI exercise of per-sub-agent routing, so you MUST emit a -**Routing report** (see the Execution order) recording, per role, the model you requested and whether the -`task` tool honored it — to stdout always, and in the PR body when you open one. +> Per-role model routing (premium models for writer/factual/examples) is a **skill feature that only takes +> effect on hosts that honor the `task` tool's `model` parameter** (e.g. the local Copilot CLI). The gh-aw CI +> sandbox does **not** plumb per-sub-agent models to the actual API call, so passing them here is cosmetic and +> only adds risk. Do not pass per-role models, and do not emit a routing report on CI. ## Scope environment @@ -215,16 +226,19 @@ A1. **Discover (lightweight).** Resolve the placeholder files into an explicit l pwsh .agents/skills/api-docs/scripts/docs-tool.ps1 resolve-scope new && cd .. ``` -A2. **Write (per batch).** For each batch, launch the **writer** sub-agent (`agents/writer.md`, model from its - header) with the resolved file list. It reads the C# source, fills only the empty/`To be added.` fields, - and edits the XML in place. A type it cannot document with certainty keeps its placeholder (a `DEFERRED` - line) so the next run re-detects it. +A2. **Write (per batch).** For each batch, launch the **writer** sub-agent (`agents/writer.md`) as a + background `task` (no per-role model) with the resolved file list, and await it with + `read_agent(wait: true)`. It reads the C# source, fills only the empty/`To be added.` fields, and edits the + XML in place. A type it cannot document with certainty keeps its placeholder (a `DEFERRED` line) so the + next run re-detects it. -A3. **Review the written batch.** Run the deterministic linter, then launch the **three** reviewers in parallel - (`reviewer-factual`, `reviewer-examples`, `reviewer-quality`), each on the batch's files with its assigned - model. Feed all findings to `review-synthesizer`. +A3. **Review the written batch.** Run the deterministic linter, then launch the **three** reviewers as + background `task`s in parallel (`reviewer-factual`, `reviewer-examples`, `reviewer-quality`) on the + batch's files (no per-role model), awaiting them per the anti-termination rule. **You** synthesize their + findings — there is no synthesizer sub-agent. -A4. **Fix CRITICAL findings** by editing the XML directly. Skip MINOR/style for the automated run. +A4. **Fix CRITICAL findings yourself** — **you (the orchestrator) edit the XML directly** in the foreground. + Do **not** launch a sub-agent for these fixes. Skip MINOR/style for the automated run. > If `resolve-scope new` returns **no** placeholder files (the common case once docs are filled), pass A is a > no-op — skip straight to pass R. @@ -248,16 +262,17 @@ R2. **Lint** the scope (deterministic, no model): pwsh .agents/skills/api-docs/scripts/docs-tool.ps1 lint "$SCOPE" && cd .. ``` -R3. **Review (three reviewers in parallel)** on the resolved file list, each via the `task` tool with its - assigned model; they report only. Feed the linter output + all three reviewers' findings to - `review-synthesizer`. +R3. **Review (three reviewers in parallel)** on the resolved file list as background `task`s (no per-role + model), awaiting them per the anti-termination rule; they report only. **You** synthesize the linter + output + all three reviewers' findings yourself — there is no synthesizer sub-agent. -R4. **Fix (gated)** by editing the XML directly, in priority order: (a) all **CRITICAL** findings, (b) - **obsolete-in-example** findings (the text/font slice has legacy `paint.TextSize` / `TextAlign` / - `DrawText(string,x,y,paint)` examples — migrate them to `SKFont`), (c) where a central type is example-poor - (`SKFont`, `SKTypeface`, `SKPaint`), add one correct, **compiling** example, porting the `SKCanvas`/`SKShader` - quality bar. Use the **writer** sub-agent for the edits. **Budget:** if you pass ~60 minutes total, stop - fixing, validate what you have, and open the PR — a smaller validated PR beats none. +R4. **Fix (gated) yourself** — **you (the orchestrator) edit the XML directly** in the foreground; do **not** + launch a sub-agent. Priority order: (a) all **CRITICAL** findings, (b) **obsolete-in-example** findings (the + text/font slice has legacy `paint.TextSize` / `TextAlign` / `DrawText(string,x,y,paint)` examples — migrate + them to `SKFont`), (c) where a central type is example-poor (`SKFont`, `SKTypeface`, `SKPaint`), add one + correct, **compiling** example, porting the `SKCanvas`/`SKShader` quality bar. **Budget:** once the + reviewers report, timebox fixing to ~10 minutes — then stop, validate what you have, and open the PR. A + smaller validated PR beats none. ### Finalize @@ -271,21 +286,6 @@ V. **Validate (replaces merge).** This gate makes direct editing safe — it mus each is well-formed, has unchanged signature counts, and changed **only** inside ``. Do **not** run `docs-format-docs` — formatting runs automatically as a post-step. -ROUTING. **Routing report (ALWAYS).** Print to stdout a block delimited exactly like this, filled in from what - the `task` tool actually did, so the routing eval is auditable even if no edits were made: - ``` - === ROUTING REPORT === - reviewer-factual | requested: gpt-5.5 | honored: | note: ... - reviewer-examples | requested: claude-opus-4.6 | honored: | note: ... - reviewer-quality | requested: claude-sonnet-4.6| honored: | note: ... - review-synthesizer | requested: claude-sonnet-4.6| honored: | note: ... - writer | requested: claude-opus-4.6 | honored: | note: ... - === END ROUTING REPORT === - ``` - Base "honored" on observable evidence: whether the `task` call accepted the `model` parameter, any - model-not-supported error the sandbox surfaced, and any self-reported model from the sub-agent. If you fell - back to a single model for all roles, say so explicitly. - C. **Commit and PR.** If any pass produced edits: ```bash git checkout -b automation/write-api-docs @@ -295,12 +295,12 @@ C. **Commit and PR.** If any pass produced edits: Then use the `create_pull_request` tool: - Branch: `automation/write-api-docs` - Title: `Fill and review API documentation` - - Body: include (a) what pass A filled (file count) and what pass R reviewed (scope + file count), (b) the - **Routing report** block verbatim, (c) a short **Findings summary** (counts by severity + the synthesizer's - machine `FINDING |` block), and (d) what you fixed vs deferred. + - Body: include (a) what pass A filled (file count) and what pass R reviewed (scope + file count), (b) a + short **Findings summary** (counts by severity + the machine `FINDING |` block), and (c) what you fixed + vs deferred. If there are no documentation changes after validation, call the `noop` tool instead — but still print the -Routing report and Findings summary to stdout first so the routing eval is observable in the run logs. +Findings summary to stdout first. ## Critical rules @@ -314,10 +314,15 @@ Routing report and Findings summary to stdout first so the routing eval is obser - **Every code example you add or change must compile** against the real `SkiaSharp.dll` (bootstrapped in a pre-step) and use **no obsolete members** (see `references/obsolete-api-map.md`). A non-compiling example is worse than none. +- **No terminal background agent.** The only sub-agents you launch are the pass-A **writer** (bulk placeholder + fill) and the **reviewers**. ALL fixing, synthesis, validation, committing, and PR creation is **your own + foreground work** — never delegate the terminal fix/validate/PR to a sub-agent. The failure mode this avoids: + backgrounding a "fixer" sub-agent and then ending your turn before it (and the PR) complete, which kills the + session with no PR. - **Budget awareness:** Prioritize reaching validate + PR. Pass A (add) is usually a no-op now, so spend the - budget on pass R. Do not re-run reviewers unnecessarily. **If you pass ~60 minutes total and haven't reached - step V, stop fixing, validate what you have, and open the PR.** A smaller validated PR beats none — but never - skip step V, and always print the Routing report. + budget on pass R. Do not re-run reviewers unnecessarily. **Once the reviewers report, timebox your fixing to + ~10 minutes; if you exceed it, stop fixing, validate what you have, and open the PR.** A smaller validated PR + beats none — but never skip step V. - **NEVER end a turn without a tool call while waiting for agents.** When you launch a background agent, you MUST call `read_agent` with `wait: true` in the SAME response. Outputting "waiting" and ending the turn terminates the session and loses all work. @@ -325,8 +330,9 @@ Routing report and Findings summary to stdout first so the routing eval is obser - **Multiple agents:** launch all, then `read_agent` the first with `wait: true`; when it returns, read the next, and so on. Keep an active `read_agent` call at all times until all agents complete. - **FORBIDDEN:** launching agents → "Waiting for them to complete" → ending the turn. This KILLS the session. -- **COMPLETION GATE:** Your session is NOT complete until you have printed the **Routing report** AND called - `create_pull_request` or `noop`. If you think you're done but did neither, retrace your steps and finish. +- **COMPLETION GATE:** Your session is NOT complete until **you** have called `create_pull_request` or `noop` + yourself. If you think you're done but did neither, retrace your steps and finish. Reaching this gate is your + own job, not a sub-agent's. ## Path differences from SKILL.md From 39fdf158866c0f14f12005120d5107da3e126373 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Fri, 26 Jun 2026 00:42:20 +0200 Subject: [PATCH 6/7] Harden PR-branch creation so the agent never clobbers the dispatch ref MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Validation run 28203165066 produced a real PR (the orchestration fix worked), but the agent rationalized out of step C: it saw it was already on the dispatch ref (a feature branch ahead of main), decided to "commit here and create the PR from this branch," and skipped `git checkout -b automation/write-api-docs`. Because safe-outputs preserves the committed branch and force-resets it (recreate_ref:true), this force-overwrote the workflow's own source branch. Fix: make step C unconditional — `git checkout -B automation/write-api-docs` before committing, even when already on a feature branch ahead of main, staging only SkiaSharpAPI/. Add a matching Critical rule explaining that committing on the dispatch ref destroys it under recreate_ref. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/auto-api-docs-writer.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/auto-api-docs-writer.md b/.github/workflows/auto-api-docs-writer.md index 11f14e6..f2fe87c 100644 --- a/.github/workflows/auto-api-docs-writer.md +++ b/.github/workflows/auto-api-docs-writer.md @@ -286,12 +286,17 @@ V. **Validate (replaces merge).** This gate makes direct editing safe — it mus each is well-formed, has unchanged signature counts, and changed **only** inside ``. Do **not** run `docs-format-docs` — formatting runs automatically as a post-step. -C. **Commit and PR.** If any pass produced edits: +C. **Commit and PR.** If any pass produced edits, **always move to the dedicated `automation/write-api-docs` + branch first** — use `-B` so it works whether or not the branch exists, and stage **only** `SkiaSharpAPI/`: ```bash - git checkout -b automation/write-api-docs + git checkout -B automation/write-api-docs git add SkiaSharpAPI/ git commit -m "Fill and review API documentation" ``` + **Do this even if you are already on a feature branch that is ahead of `main`.** Never commit doc changes + onto the branch the workflow was dispatched from: that branch may be the workflow's own source branch, and + `safe-outputs` (`recreate_ref: true`) force-overwrites the PR head ref — committing on the dispatch ref + would destroy the workflow source. Staging only `SkiaSharpAPI/` also keeps any workflow files out of the PR. Then use the `create_pull_request` tool: - Branch: `automation/write-api-docs` - Title: `Fill and review API documentation` @@ -330,6 +335,11 @@ Findings summary to stdout first. - **Multiple agents:** launch all, then `read_agent` the first with `wait: true`; when it returns, read the next, and so on. Keep an active `read_agent` call at all times until all agents complete. - **FORBIDDEN:** launching agents → "Waiting for them to complete" → ending the turn. This KILLS the session. +- **Always commit on the dedicated `automation/write-api-docs` branch (`git checkout -B`), never on the branch + you were dispatched from.** `safe-outputs` preserves the branch you commit on and force-overwrites it + (`recreate_ref: true`). If you commit on the dispatch ref (which can be the workflow's own source branch), + that branch is destroyed. Switch branches before committing even when already on a feature branch ahead of + `main`, and stage only `SkiaSharpAPI/`. - **COMPLETION GATE:** Your session is NOT complete until **you** have called `create_pull_request` or `noop` yourself. If you think you're done but did neither, retrace your steps and finish. Reaching this gate is your own job, not a sub-agent's. From 10fe960c3e47b124576831e1a455cdb498deecf2 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Fri, 26 Jun 2026 09:16:49 +0200 Subject: [PATCH 7/7] Host-side guarantee: put the agent on a throwaway PR branch before it runs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause of the run-156 branch clobber: gh-aw's checkout step makes the DISPATCH REF the agent's working branch (git checkout -B ). When dispatched from a feature branch, the agent starts on that branch; it then committed its doc work there instead of switching to automation/write-api-docs, and safe-outputs (preserve_branch_name + recreate_ref) adopted that branch name as the PR head and force-recreated it — erasing the workflow source commits. Relying on the agent to switch branches is fragile (it rationalized staying put because the branch was "already ahead by 1 commit"). Add a host pre-agent step that renames the working branch to automation/write-api-docs before the agent starts, so every commit and the PR head land on a throwaway branch regardless of which ref triggered the run. The prompt rule remains as defense-in-depth. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../workflows/auto-api-docs-writer.lock.yml | 32 ++++++++++--------- .github/workflows/auto-api-docs-writer.md | 12 +++++++ 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/.github/workflows/auto-api-docs-writer.lock.yml b/.github/workflows/auto-api-docs-writer.lock.yml index 097b906..df33f70 100644 --- a/.github/workflows/auto-api-docs-writer.lock.yml +++ b/.github/workflows/auto-api-docs-writer.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"a977482f3da156d5a71f706afff5cf60162806e9c08090e6f4f47753bdc5dee7","compiler_version":"v0.71.5","strict":true,"agent_id":"copilot","agent_model":"claude-sonnet-4.6"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"c8adc861f414bca84111f83671cb734b41d6c0fb5c5c265cb8621961a8d1c9ff","compiler_version":"v0.71.5","strict":true,"agent_id":"copilot","agent_model":"claude-sonnet-4.6"} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache","sha":"0057852bfaa89a56745cba8c7296529d2fc39830","version":"v4"},{"repo":"actions/checkout","sha":"34e114876b0b11c390a56381ad16ebd13914f8d5","version":"v4"},{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/download-artifact","sha":"d3f86a106a0bac45b974a628896c90dbdf5c8093","version":"v4"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-dotnet","sha":"67a3573c9a986a3f9c594539f4ab511d57bb3ce9","version":"v4"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"actions/upload-artifact","sha":"ea165f8d65b6e75b540449e92b4886f43607fa02","version":"v4"},{"repo":"github/gh-aw-actions/setup","sha":"b8068426813005612b960b5ab0b8bd2c27142323","version":"v0.71.5"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.40","digest":"sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.40@sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40","digest":"sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40@sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.40","digest":"sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.40@sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51"},{"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"}]} # ___ _ _ # / _ \ | | (_) @@ -201,23 +201,23 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_834eec06139fdaf5_EOF' + cat << 'GH_AW_PROMPT_8cc691f23824d60d_EOF' - GH_AW_PROMPT_834eec06139fdaf5_EOF + GH_AW_PROMPT_8cc691f23824d60d_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_834eec06139fdaf5_EOF' + cat << 'GH_AW_PROMPT_8cc691f23824d60d_EOF' Tools: create_pull_request, missing_tool, missing_data, noop - GH_AW_PROMPT_834eec06139fdaf5_EOF + GH_AW_PROMPT_8cc691f23824d60d_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" - cat << 'GH_AW_PROMPT_834eec06139fdaf5_EOF' + cat << 'GH_AW_PROMPT_8cc691f23824d60d_EOF' - GH_AW_PROMPT_834eec06139fdaf5_EOF + GH_AW_PROMPT_8cc691f23824d60d_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_834eec06139fdaf5_EOF' + cat << 'GH_AW_PROMPT_8cc691f23824d60d_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -249,12 +249,12 @@ jobs: - **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). - GH_AW_PROMPT_834eec06139fdaf5_EOF + GH_AW_PROMPT_8cc691f23824d60d_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_834eec06139fdaf5_EOF' + cat << 'GH_AW_PROMPT_8cc691f23824d60d_EOF' {{#runtime-import .github/workflows/auto-api-docs-writer.md}} - GH_AW_PROMPT_834eec06139fdaf5_EOF + GH_AW_PROMPT_8cc691f23824d60d_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -436,6 +436,8 @@ jobs: 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: Use a dedicated PR branch (never the dispatch ref) + run: "git checkout -B automation/write-api-docs\necho \"Working branch: $(git branch --show-current)\"\n" - name: Download regenerated docs uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: @@ -461,9 +463,9 @@ jobs: 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_5a9750efa4c49447_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_0070cf142444b1c9_EOF' {"create_pull_request":{"base_branch":"main","draft":false,"max":1,"max_patch_files":100,"max_patch_size":1024,"preserve_branch_name":true,"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"],"recreate_ref":true},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_5a9750efa4c49447_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_0070cf142444b1c9_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -669,7 +671,7 @@ jobs: 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_84a9b7fc8e0ed44c_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_29ff91a154f7cbdb_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -717,7 +719,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_84a9b7fc8e0ed44c_EOF + GH_AW_MCP_CONFIG_29ff91a154f7cbdb_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true diff --git a/.github/workflows/auto-api-docs-writer.md b/.github/workflows/auto-api-docs-writer.md index f2fe87c..d3283cf 100644 --- a/.github/workflows/auto-api-docs-writer.md +++ b/.github/workflows/auto-api-docs-writer.md @@ -127,6 +127,18 @@ safe-outputs: # -- Pre-agent steps (host) ------------------------------------------- pre-agent-steps: + # gh-aw's checkout step makes the dispatch ref the working branch. If the agent + # commits there, safe-outputs (recreate_ref) force-overwrites that ref — which + # destroys the workflow's own source branch when the run is dispatched from a + # feature branch (e.g. a dev/* branch under review). Rename the working branch + # up-front so every commit and the PR head land on a throwaway branch, no matter + # which ref triggered the run. This is the host-side guarantee that backs up the + # "always commit on automation/write-api-docs" rule in the prompt. + - name: Use a dedicated PR branch (never the dispatch ref) + run: | + git checkout -B automation/write-api-docs + echo "Working branch: $(git branch --show-current)" + - name: Download regenerated docs uses: actions/download-artifact@v4 with: