From b9ed9e6c6eb50dc3f748e050ce5de6cfe0cca7a7 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Thu, 25 Jun 2026 18:58:21 +0200 Subject: [PATCH 01/21] 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 02/21] 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 03/21] 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 04/21] 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 05/21] 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 06/21] 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 07/21] 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: From e30f07ba470c95a6ba94862c87f5403b228c690e Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Fri, 26 Jun 2026 20:35:46 +0200 Subject: [PATCH 08/21] Add custom base/dest branch inputs for add-at-scale testing Parameterize the docs PR base (docs_base_branch) and PR head (docs_head_branch) as workflow_dispatch inputs so the writer can be tested against an older docs state where many 'To be added.' placeholders still exist. - base-branch -> ${{ inputs.docs_base_branch || 'main' }}; gh-aw propagates it to the safeoutputs config.json, the safe_outputs checkout ref, and the handler env (all interpolation-safe). - regenerate-stubs aligns the docs submodule to docs_base_branch (was hardcoded main). - host PR-branch step checks out docs_head_branch (was hardcoded automation/write-api-docs). - prompt no longer hardcodes the PR branch name: commit on the host-prepared current branch, never the dispatch ref. Backward compatible: schedule/push runs default base=main, head=automation/write-api-docs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../workflows/auto-api-docs-writer.lock.yml | 60 ++++++++++++------- .github/workflows/auto-api-docs-writer.md | 57 +++++++++++------- 2 files changed, 73 insertions(+), 44 deletions(-) diff --git a/.github/workflows/auto-api-docs-writer.lock.yml b/.github/workflows/auto-api-docs-writer.lock.yml index df33f70..9952990 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":"c8adc861f414bca84111f83671cb734b41d6c0fb5c5c265cb8621961a8d1c9ff","compiler_version":"v0.71.5","strict":true,"agent_id":"copilot","agent_model":"claude-sonnet-4.6"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"d05ad88e1baac39ee9cd2b1143f19be3b3ec49de85776dac967adae490dc5aec","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"}]} # ___ _ _ # / _ \ | | (_) @@ -68,6 +68,16 @@ name: "Auto API Docs Writer" description: Agent caller context (used internally by Agentic Workflows). required: false type: string + docs_base_branch: + default: main + description: Docs branch the PR targets and stubs align to (default main). Point at an older branch to demo add-at-scale. + required: false + type: string + docs_head_branch: + default: automation/write-api-docs + description: Throwaway PR head branch the agent commits to (force-recreated each run). + required: false + type: string skiasharp_branch: default: main description: SkiaSharp branch to use for scripts and references @@ -201,23 +211,23 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_8cc691f23824d60d_EOF' + cat << 'GH_AW_PROMPT_cced14acabc5e5d9_EOF' - GH_AW_PROMPT_8cc691f23824d60d_EOF + GH_AW_PROMPT_cced14acabc5e5d9_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_8cc691f23824d60d_EOF' + cat << 'GH_AW_PROMPT_cced14acabc5e5d9_EOF' Tools: create_pull_request, missing_tool, missing_data, noop - GH_AW_PROMPT_8cc691f23824d60d_EOF + GH_AW_PROMPT_cced14acabc5e5d9_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" - cat << 'GH_AW_PROMPT_8cc691f23824d60d_EOF' + cat << 'GH_AW_PROMPT_cced14acabc5e5d9_EOF' - GH_AW_PROMPT_8cc691f23824d60d_EOF + GH_AW_PROMPT_cced14acabc5e5d9_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_8cc691f23824d60d_EOF' + cat << 'GH_AW_PROMPT_cced14acabc5e5d9_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -249,12 +259,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_8cc691f23824d60d_EOF + GH_AW_PROMPT_cced14acabc5e5d9_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_8cc691f23824d60d_EOF' + cat << 'GH_AW_PROMPT_cced14acabc5e5d9_EOF' {{#runtime-import .github/workflows/auto-api-docs-writer.md}} - GH_AW_PROMPT_8cc691f23824d60d_EOF + GH_AW_PROMPT_cced14acabc5e5d9_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -436,8 +446,10 @@ 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" + - env: + DOCS_HEAD_BRANCH: ${{ inputs.docs_head_branch || 'automation/write-api-docs' }} + name: Use a dedicated PR branch (never the dispatch ref) + run: "git checkout -B \"$DOCS_HEAD_BRANCH\"\necho \"Working branch: $(git branch --show-current)\"\n" - name: Download regenerated docs uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: @@ -463,9 +475,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_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_0070cf142444b1c9_EOF + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_5c9d2d399ebe5777_EOF' + {"create_pull_request":{"base_branch":"${{ inputs.docs_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_5c9d2d399ebe5777_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -671,7 +683,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_29ff91a154f7cbdb_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_ab33ab45d0404c02_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -719,7 +731,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_29ff91a154f7cbdb_EOF + GH_AW_MCP_CONFIG_ab33ab45d0404c02_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -1328,12 +1340,14 @@ jobs: ref: ${{ inputs.skiasharp_branch || 'main' }} repository: mono/SkiaSharp submodules: recursive - - name: Align docs to latest main + - name: Align docs to base branch run: | cd docs - git fetch origin main - git checkout -B automation/write-api-docs origin/main + git fetch origin "$DOCS_BASE_BRANCH" + git checkout -B stub-base "origin/$DOCS_BASE_BRANCH" cd .. + env: + DOCS_BASE_BRANCH: ${{ inputs.docs_base_branch || 'main' }} shell: bash - name: Setup .NET uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4 @@ -1433,7 +1447,7 @@ jobs: 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 + ref: ${{ inputs.docs_base_branch || 'main' }} token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} persist-credentials: false fetch-depth: 1 @@ -1468,7 +1482,7 @@ jobs: 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_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_pull_request\":{\"base_branch\":\"${{ inputs.docs_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 }} diff --git a/.github/workflows/auto-api-docs-writer.md b/.github/workflows/auto-api-docs-writer.md index d3283cf..36e82ac 100644 --- a/.github/workflows/auto-api-docs-writer.md +++ b/.github/workflows/auto-api-docs-writer.md @@ -20,6 +20,16 @@ on: required: false default: "main" type: string + docs_base_branch: + description: "Docs branch the PR targets and stubs align to (default main). Point at an older branch to demo add-at-scale." + required: false + default: "main" + type: string + docs_head_branch: + description: "Throwaway PR head branch the agent commits to (force-recreated each run)." + required: false + default: "automation/write-api-docs" + type: string # -- Custom jobs ------------------------------------------------------- # Stub regeneration runs mdoc to produce the XML reference stubs. mdoc.exe is a @@ -40,12 +50,14 @@ jobs: ref: ${{ inputs.skiasharp_branch || 'main' }} fetch-depth: 1 submodules: recursive - - name: Align docs to latest main + - name: Align docs to base branch shell: bash + env: + DOCS_BASE_BRANCH: ${{ inputs.docs_base_branch || 'main' }} run: | cd docs - git fetch origin main - git checkout -B automation/write-api-docs origin/main + git fetch origin "$DOCS_BASE_BRANCH" + git checkout -B stub-base "origin/$DOCS_BASE_BRANCH" cd .. - name: Setup .NET uses: actions/setup-dotnet@v4 @@ -121,7 +133,7 @@ permissions: safe-outputs: create-pull-request: draft: false - base-branch: main + base-branch: ${{ inputs.docs_base_branch || 'main' }} preserve-branch-name: true recreate-ref: true @@ -132,11 +144,14 @@ pre-agent-steps: # 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. + # which ref triggered the run. The branch name is the `docs_head_branch` input + # (default automation/write-api-docs). This is the host-side guarantee that backs + # up the "commit on the branch you're already on" rule in the prompt. - name: Use a dedicated PR branch (never the dispatch ref) + env: + DOCS_HEAD_BRANCH: ${{ inputs.docs_head_branch || 'automation/write-api-docs' }} run: | - git checkout -B automation/write-api-docs + git checkout -B "$DOCS_HEAD_BRANCH" echo "Working branch: $(git branch --show-current)" - name: Download regenerated docs @@ -298,19 +313,19 @@ 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, **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/`: +C. **Commit and PR.** If any pass produced edits, **commit on the branch you are already on** — the host + prepared a dedicated throwaway PR branch for you before you started (it is **not** the dispatch ref). Do + **not** switch or create another branch. Stage **only** `SkiaSharpAPI/`: ```bash - 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` + **Never `git checkout` 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: the current working branch (leave the `create_pull_request` branch to the host default — do not + hardcode a name) - Title: `Fill and review API documentation` - 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 @@ -347,11 +362,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/`. +- **Always commit on the branch you are already on** (the host prepared a dedicated throwaway PR branch before + you started) and **never `git checkout` 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. Do not switch or create another branch + even when the current branch looks like 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 cdae111fd5ece024f1dac4776f67b8a2747ef7bd Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Fri, 26 Jun 2026 21:10:12 +0200 Subject: [PATCH 09/21] Fix stub-align for arbitrary base branch: checkout FETCH_HEAD A shallow submodule clone has no origin/ remote-tracking ref for an arbitrary docs_base_branch, so 'git checkout -B stub-base origin/' fails (it only worked for main, which is tracked). Use FETCH_HEAD, set by the preceding fetch, which resolves for any branch. 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 | 2 +- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/auto-api-docs-writer.lock.yml b/.github/workflows/auto-api-docs-writer.lock.yml index 9952990..b88e1ba 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":"d05ad88e1baac39ee9cd2b1143f19be3b3ec49de85776dac967adae490dc5aec","compiler_version":"v0.71.5","strict":true,"agent_id":"copilot","agent_model":"claude-sonnet-4.6"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"7958f062a4097c14ad20e32ad7ab46c2766bb801c9a949eaa5efa62bdb645bc5","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"}]} # ___ _ _ # / _ \ | | (_) @@ -211,23 +211,23 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_cced14acabc5e5d9_EOF' + cat << 'GH_AW_PROMPT_95883117998d1e89_EOF' - GH_AW_PROMPT_cced14acabc5e5d9_EOF + GH_AW_PROMPT_95883117998d1e89_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_cced14acabc5e5d9_EOF' + cat << 'GH_AW_PROMPT_95883117998d1e89_EOF' Tools: create_pull_request, missing_tool, missing_data, noop - GH_AW_PROMPT_cced14acabc5e5d9_EOF + GH_AW_PROMPT_95883117998d1e89_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" - cat << 'GH_AW_PROMPT_cced14acabc5e5d9_EOF' + cat << 'GH_AW_PROMPT_95883117998d1e89_EOF' - GH_AW_PROMPT_cced14acabc5e5d9_EOF + GH_AW_PROMPT_95883117998d1e89_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_cced14acabc5e5d9_EOF' + cat << 'GH_AW_PROMPT_95883117998d1e89_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -259,12 +259,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_cced14acabc5e5d9_EOF + GH_AW_PROMPT_95883117998d1e89_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_cced14acabc5e5d9_EOF' + cat << 'GH_AW_PROMPT_95883117998d1e89_EOF' {{#runtime-import .github/workflows/auto-api-docs-writer.md}} - GH_AW_PROMPT_cced14acabc5e5d9_EOF + GH_AW_PROMPT_95883117998d1e89_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -475,9 +475,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_5c9d2d399ebe5777_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_a1e6933a0a018a4c_EOF' {"create_pull_request":{"base_branch":"${{ inputs.docs_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_5c9d2d399ebe5777_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_a1e6933a0a018a4c_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -683,7 +683,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_ab33ab45d0404c02_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_ee9cf97559e42384_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -731,7 +731,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_ab33ab45d0404c02_EOF + GH_AW_MCP_CONFIG_ee9cf97559e42384_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -1344,7 +1344,7 @@ jobs: run: | cd docs git fetch origin "$DOCS_BASE_BRANCH" - git checkout -B stub-base "origin/$DOCS_BASE_BRANCH" + git checkout -B stub-base FETCH_HEAD cd .. env: DOCS_BASE_BRANCH: ${{ inputs.docs_base_branch || 'main' }} diff --git a/.github/workflows/auto-api-docs-writer.md b/.github/workflows/auto-api-docs-writer.md index 36e82ac..969ef01 100644 --- a/.github/workflows/auto-api-docs-writer.md +++ b/.github/workflows/auto-api-docs-writer.md @@ -57,7 +57,7 @@ jobs: run: | cd docs git fetch origin "$DOCS_BASE_BRANCH" - git checkout -B stub-base "origin/$DOCS_BASE_BRANCH" + git checkout -B stub-base FETCH_HEAD cd .. - name: Setup .NET uses: actions/setup-dotnet@v4 From a9a9ba277b2f4446bfc7399c75a6dc336e4195a5 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Fri, 26 Jun 2026 22:35:13 +0200 Subject: [PATCH 10/21] Add review_scope input and stop staging generated docs files - review_scope workflow_dispatch input (default group:text) drives the review pass scope, so other doc groups can be reviewed one at a time. - Commit step now unstages generated files (index.xml, ns-*.xml, _filter.xml, FrameworksIndex/) that stub regeneration rewrites, keeping them out of the PR. Exposed by the add-at-scale run against an old base where the regenerated generated files diverged from the base. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../workflows/auto-api-docs-writer.lock.yml | 41 +++++++++++-------- .github/workflows/auto-api-docs-writer.md | 13 +++++- 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/.github/workflows/auto-api-docs-writer.lock.yml b/.github/workflows/auto-api-docs-writer.lock.yml index b88e1ba..218ab65 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":"7958f062a4097c14ad20e32ad7ab46c2766bb801c9a949eaa5efa62bdb645bc5","compiler_version":"v0.71.5","strict":true,"agent_id":"copilot","agent_model":"claude-sonnet-4.6"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"4f32abed04317f2f78e354b57252c0876e319f7455ad775b6bd6e51134b88410","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"}]} # ___ _ _ # / _ \ | | (_) @@ -78,6 +78,11 @@ name: "Auto API Docs Writer" description: Throwaway PR head branch the agent commits to (force-recreated each run). required: false type: string + review_scope: + default: group:text + description: Scope selector for the review pass (e.g. group:text, group:image, ns:HarfBuzzSharp, all). + required: false + type: string skiasharp_branch: default: main description: SkiaSharp branch to use for scripts and references @@ -211,23 +216,23 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_95883117998d1e89_EOF' + cat << 'GH_AW_PROMPT_269dd275a6dd7d1c_EOF' - GH_AW_PROMPT_95883117998d1e89_EOF + GH_AW_PROMPT_269dd275a6dd7d1c_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_95883117998d1e89_EOF' + cat << 'GH_AW_PROMPT_269dd275a6dd7d1c_EOF' Tools: create_pull_request, missing_tool, missing_data, noop - GH_AW_PROMPT_95883117998d1e89_EOF + GH_AW_PROMPT_269dd275a6dd7d1c_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" - cat << 'GH_AW_PROMPT_95883117998d1e89_EOF' + cat << 'GH_AW_PROMPT_269dd275a6dd7d1c_EOF' - GH_AW_PROMPT_95883117998d1e89_EOF + GH_AW_PROMPT_269dd275a6dd7d1c_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_95883117998d1e89_EOF' + cat << 'GH_AW_PROMPT_269dd275a6dd7d1c_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -259,12 +264,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_95883117998d1e89_EOF + GH_AW_PROMPT_269dd275a6dd7d1c_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_95883117998d1e89_EOF' + cat << 'GH_AW_PROMPT_269dd275a6dd7d1c_EOF' {{#runtime-import .github/workflows/auto-api-docs-writer.md}} - GH_AW_PROMPT_95883117998d1e89_EOF + GH_AW_PROMPT_269dd275a6dd7d1c_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -455,8 +460,10 @@ 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: + REVIEW_SCOPE: ${{ inputs.review_scope || 'group:text' }} + name: Record review scope + run: "printf '%s' \"$REVIEW_SCOPE\" > 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) @@ -475,9 +482,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_a1e6933a0a018a4c_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_78bd0292e973629a_EOF' {"create_pull_request":{"base_branch":"${{ inputs.docs_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_a1e6933a0a018a4c_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_78bd0292e973629a_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -683,7 +690,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_ee9cf97559e42384_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_2473434c85321deb_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -731,7 +738,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_ee9cf97559e42384_EOF + GH_AW_MCP_CONFIG_2473434c85321deb_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 969ef01..f03589f 100644 --- a/.github/workflows/auto-api-docs-writer.md +++ b/.github/workflows/auto-api-docs-writer.md @@ -30,6 +30,11 @@ on: required: false default: "automation/write-api-docs" type: string + review_scope: + description: "Scope selector for the review pass (e.g. group:text, group:image, ns:HarfBuzzSharp, all)." + required: false + default: "group:text" + type: string # -- Custom jobs ------------------------------------------------------- # Stub regeneration runs mdoc to produce the XML reference stubs. mdoc.exe is a @@ -166,8 +171,10 @@ pre-agent-steps: # 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 + env: + REVIEW_SCOPE: ${{ inputs.review_scope || 'group:text' }} run: | - printf '%s' "group:text" > review-scope.txt + printf '%s' "$REVIEW_SCOPE" > review-scope.txt echo "Review scope (existing docs): $(cat review-scope.txt)" - name: Clone SkiaSharp (shallow, with submodules) @@ -315,9 +322,11 @@ V. **Validate (replaces merge).** This gate makes direct editing safe — it mus C. **Commit and PR.** If any pass produced edits, **commit on the branch you are already on** — the host prepared a dedicated throwaway PR branch for you before you started (it is **not** the dispatch ref). Do - **not** switch or create another branch. Stage **only** `SkiaSharpAPI/`: + **not** switch or create another branch. Stage **only** hand-edited type docs under `SkiaSharpAPI/`, and + **unstage the generated files** (stub regeneration rewrites them; they must never appear in the PR): ```bash git add SkiaSharpAPI/ + git reset -q -- SkiaSharpAPI/index.xml 'SkiaSharpAPI/ns-*.xml' SkiaSharpAPI/_filter.xml SkiaSharpAPI/FrameworksIndex/ git commit -m "Fill and review API documentation" ``` **Never `git checkout` the branch the workflow was dispatched from.** That branch may be the workflow's own From 09747703fb5c017f8e9a30e4fd71d5e4071b18c4 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Mon, 29 Jun 2026 18:04:17 +0200 Subject: [PATCH 11/21] Run on Opus 4.7 as a single agent (drop sub-agent fan-out) The gh-aw sandbox does not honor per-sub-agent model routing, so the multi-agent orchestration added complexity (and caused the no-PR failure) without benefit. - engine.model -> claude-opus-4.7 (was claude-sonnet-4.6). - One agent does add + review + fix + validate + PR; no task sub-agents, no per-role models, no synthesizer. - Repoint skill references to the collapsed layout (references/adding.md, references/reviewing.md, references/scope-resolution.md, references/validation.md). - Trim the now-moot anti-termination / no-terminal-background-agent rules. 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 | 111 +++++++----------- 2 files changed, 59 insertions(+), 90 deletions(-) diff --git a/.github/workflows/auto-api-docs-writer.lock.yml b/.github/workflows/auto-api-docs-writer.lock.yml index 218ab65..b5d5603 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":"4f32abed04317f2f78e354b57252c0876e319f7455ad775b6bd6e51134b88410","compiler_version":"v0.71.5","strict":true,"agent_id":"copilot","agent_model":"claude-sonnet-4.6"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"b44b876aa9af16cfa97e34ba16a278d6889f9e3865264fb308a8d8e514161e2f","compiler_version":"v0.71.5","strict":true,"agent_id":"copilot","agent_model":"claude-opus-4.7"} # 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"}]} # ___ _ _ # / _ \ | | (_) @@ -131,7 +131,7 @@ jobs: env: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" - GH_AW_INFO_MODEL: "claude-sonnet-4.6" + GH_AW_INFO_MODEL: "claude-opus-4.7" GH_AW_INFO_VERSION: "1.0.40" GH_AW_INFO_AGENT_VERSION: "1.0.40" GH_AW_INFO_CLI_VERSION: "v0.71.5" @@ -216,23 +216,23 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_269dd275a6dd7d1c_EOF' + cat << 'GH_AW_PROMPT_28a13ee42a798844_EOF' - GH_AW_PROMPT_269dd275a6dd7d1c_EOF + GH_AW_PROMPT_28a13ee42a798844_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_269dd275a6dd7d1c_EOF' + cat << 'GH_AW_PROMPT_28a13ee42a798844_EOF' Tools: create_pull_request, missing_tool, missing_data, noop - GH_AW_PROMPT_269dd275a6dd7d1c_EOF + GH_AW_PROMPT_28a13ee42a798844_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" - cat << 'GH_AW_PROMPT_269dd275a6dd7d1c_EOF' + cat << 'GH_AW_PROMPT_28a13ee42a798844_EOF' - GH_AW_PROMPT_269dd275a6dd7d1c_EOF + GH_AW_PROMPT_28a13ee42a798844_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_269dd275a6dd7d1c_EOF' + cat << 'GH_AW_PROMPT_28a13ee42a798844_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -264,12 +264,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_269dd275a6dd7d1c_EOF + GH_AW_PROMPT_28a13ee42a798844_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_269dd275a6dd7d1c_EOF' + cat << 'GH_AW_PROMPT_28a13ee42a798844_EOF' {{#runtime-import .github/workflows/auto-api-docs-writer.md}} - GH_AW_PROMPT_269dd275a6dd7d1c_EOF + GH_AW_PROMPT_28a13ee42a798844_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -482,9 +482,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_78bd0292e973629a_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_eab07c5c83d9dd6b_EOF' {"create_pull_request":{"base_branch":"${{ inputs.docs_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_78bd0292e973629a_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_eab07c5c83d9dd6b_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -690,7 +690,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_2473434c85321deb_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_9df0e17070918574_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -738,7 +738,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_2473434c85321deb_EOF + GH_AW_MCP_CONFIG_9df0e17070918574_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -778,7 +778,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: claude-sonnet-4.6 + COPILOT_MODEL: claude-opus-4.7 GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt @@ -1242,7 +1242,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: claude-sonnet-4.6 + COPILOT_MODEL: claude-opus-4.7 GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_VERSION: v0.71.5 @@ -1405,7 +1405,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: "claude-sonnet-4.6" + GH_AW_ENGINE_MODEL: "claude-opus-4.7" 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 f03589f..71b08ff 100644 --- a/.github/workflows/auto-api-docs-writer.md +++ b/.github/workflows/auto-api-docs-writer.md @@ -105,14 +105,14 @@ concurrency: 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. +# Single agent, single model. The gh-aw sandbox does not honor per-sub-agent +# model routing (the task tool's `model` param is not plumbed through to the +# actual API call — verified via the api-proxy token-usage log), so there is no +# point fanning out into per-role sub-agents. One capable model does the whole +# run: add + review + fix + PR. engine: id: copilot - model: claude-sonnet-4.6 + model: claude-opus-4.7 # -- Agent tools ------------------------------------------------------- tools: @@ -209,34 +209,21 @@ post-steps: # Auto API Docs Writer -You are the **orchestrator** for the unified `add` + `review` pipeline. You run **two passes** in one job: +You are the agent 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: +a scope of **existing** docs. You do the whole job yourself — there is **no sub-agent fan-out**. 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). 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. +- `skiasharp/.agents/skills/api-docs/references/adding.md` — the direct-XML add procedure (pass A). +- `skiasharp/.agents/skills/api-docs/references/reviewing.md` — the review procedure + checks (pass R). +- `skiasharp/.agents/skills/api-docs/references/scope-resolution.md` — how a scope selector resolves to files. +- `skiasharp/.agents/skills/api-docs/references/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 +`SkiaSharpAPI/` as uncommitted working-tree changes. There is **no extract/merge JSON step** — you read and **edit the mdoc XML directly**; safety comes from the structural validator, not a merge guard. -## Model routing - -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). - -> 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 `docs-tool.ps1` lives in the SkiaSharp clone, so by default it would look for docs under @@ -253,26 +240,21 @@ DOCS_GIT_ROOT="$GITHUB_WORKSPACE" DOCS_DIR="$GITHUB_WORKSPACE/SkiaSharpAPI" ### 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. +A1. **Discover.** Resolve the placeholder files into an explicit list and shard it into ~25–40-file batches. ```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 .. ``` -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. +A2. **Write (per batch), yourself.** Following `references/adding.md`, for each file read the C# source first, + then fill only the empty/`To be added.` fields and edit the XML in place. A type you cannot document with + certainty keeps its placeholder (note it as a `DEFERRED` line) so the next run re-detects it. Process the + batches one at a time so each stays in working memory. -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. +A3. **Review the written batch.** Run the deterministic linter, then review the batch's files against the + checks in `references/reviewing.md` and note the findings. -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. +A4. **Fix CRITICAL findings yourself** 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. @@ -288,7 +270,7 @@ R1. **Resolve the review scope** (fuzzy selectors need `-Confirm:$false` in CI): 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 .. ``` - Shard >40 files into batches; the text/font slice is ~16 files (one batch). + Shard >40 files into batches and process them one at a time; the text/font slice is ~16 files (one batch). R2. **Lint** the scope (deterministic, no model): ```bash @@ -296,17 +278,16 @@ 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 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. +R3. **Review, yourself.** For each file in the resolved list, follow `references/reviewing.md`: read the C# + source first, then run the factual / example / quality checks against the `` blocks. Collect the + linter output plus your findings into one deduped list. -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. +R4. **Fix (gated) yourself** by editing the XML directly. 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:** timebox fixing to ~10 minutes — then stop, validate what you have, and + open the PR. A smaller validated PR beats none. ### Finalize @@ -349,36 +330,24 @@ Findings summary to stdout first. `MemberSignature`/`TypeSignature`, attributes, or generated files (`index.xml`, `ns-*.xml`, `_filter.xml`, `FrameworksIndex/`). The structural validator enforces this; a failure means you edited outside ``. - **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. - **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. +- **Do everything yourself, in the foreground.** Do **not** launch sub-agents — discovery, writing, review, + fixing, validation, committing, and PR creation are all your own work. The gh-aw sandbox does not honor + per-sub-agent models, and backgrounding a terminal agent and then ending your turn before the PR is created + 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. **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. - - **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. + budget on pass R. **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. - **Always commit on the branch you are already on** (the host prepared a dedicated throwaway PR branch before you started) and **never `git checkout` 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. Do not switch or create another branch even when the current branch looks like 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. +- **COMPLETION GATE:** Your session is NOT complete until you have 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 3f2edc5aeacb32a5ff966687e53bb05b3c0b26a1 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Mon, 29 Jun 2026 22:25:16 +0200 Subject: [PATCH 12/21] auto-api-docs-writer: resolve review themes from the model (no group: aliases) The api-docs skill dropped curated group: aliases, so resolve-scope no longer understands group:text. Switch review_scope to accept either a docs-tool selector (type:/ns:/match:/changed/all) or a plain-English theme that the agent expands by resolving all and selecting matching files. Default is now the plain text theme. Pass R branches on selector-vs-theme; lint runs per file for a theme. Recompiled the lock. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../workflows/auto-api-docs-writer.lock.yml | 36 ++++++++-------- .github/workflows/auto-api-docs-writer.md | 43 ++++++++++++------- 2 files changed, 46 insertions(+), 33 deletions(-) diff --git a/.github/workflows/auto-api-docs-writer.lock.yml b/.github/workflows/auto-api-docs-writer.lock.yml index b5d5603..8049fb9 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":"b44b876aa9af16cfa97e34ba16a278d6889f9e3865264fb308a8d8e514161e2f","compiler_version":"v0.71.5","strict":true,"agent_id":"copilot","agent_model":"claude-opus-4.7"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"124cf94364be81a8a4645caf7e502ab7eb63ab51fa7048aca0c00b5d24777bfe","compiler_version":"v0.71.5","strict":true,"agent_id":"copilot","agent_model":"claude-opus-4.7"} # 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"}]} # ___ _ _ # / _ \ | | (_) @@ -79,8 +79,8 @@ name: "Auto API Docs Writer" required: false type: string review_scope: - default: group:text - description: Scope selector for the review pass (e.g. group:text, group:image, ns:HarfBuzzSharp, all). + default: text + description: "Review-pass scope: a docs-tool selector (type:/ns:/match:/changed/all) or a plain-English theme the agent resolves (e.g. text, image filters)." required: false type: string skiasharp_branch: @@ -216,23 +216,23 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_28a13ee42a798844_EOF' + cat << 'GH_AW_PROMPT_5f13ab03c8884c1c_EOF' - GH_AW_PROMPT_28a13ee42a798844_EOF + GH_AW_PROMPT_5f13ab03c8884c1c_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_28a13ee42a798844_EOF' + cat << 'GH_AW_PROMPT_5f13ab03c8884c1c_EOF' Tools: create_pull_request, missing_tool, missing_data, noop - GH_AW_PROMPT_28a13ee42a798844_EOF + GH_AW_PROMPT_5f13ab03c8884c1c_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" - cat << 'GH_AW_PROMPT_28a13ee42a798844_EOF' + cat << 'GH_AW_PROMPT_5f13ab03c8884c1c_EOF' - GH_AW_PROMPT_28a13ee42a798844_EOF + GH_AW_PROMPT_5f13ab03c8884c1c_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_28a13ee42a798844_EOF' + cat << 'GH_AW_PROMPT_5f13ab03c8884c1c_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -264,12 +264,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_28a13ee42a798844_EOF + GH_AW_PROMPT_5f13ab03c8884c1c_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_28a13ee42a798844_EOF' + cat << 'GH_AW_PROMPT_5f13ab03c8884c1c_EOF' {{#runtime-import .github/workflows/auto-api-docs-writer.md}} - GH_AW_PROMPT_28a13ee42a798844_EOF + GH_AW_PROMPT_5f13ab03c8884c1c_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -461,7 +461,7 @@ jobs: name: docs-regenerated path: SkiaSharpAPI/ - env: - REVIEW_SCOPE: ${{ inputs.review_scope || 'group:text' }} + REVIEW_SCOPE: ${{ inputs.review_scope || 'text' }} name: Record review scope run: "printf '%s' \"$REVIEW_SCOPE\" > review-scope.txt\necho \"Review scope (existing docs): $(cat review-scope.txt)\"\n" - env: @@ -482,9 +482,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_eab07c5c83d9dd6b_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_0da9bd066c971f8e_EOF' {"create_pull_request":{"base_branch":"${{ inputs.docs_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_eab07c5c83d9dd6b_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_0da9bd066c971f8e_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -690,7 +690,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_9df0e17070918574_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_4ba3666662db12d8_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -738,7 +738,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_9df0e17070918574_EOF + GH_AW_MCP_CONFIG_4ba3666662db12d8_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 71b08ff..4091b46 100644 --- a/.github/workflows/auto-api-docs-writer.md +++ b/.github/workflows/auto-api-docs-writer.md @@ -31,9 +31,9 @@ on: default: "automation/write-api-docs" type: string review_scope: - description: "Scope selector for the review pass (e.g. group:text, group:image, ns:HarfBuzzSharp, all)." + description: "Review-pass scope: a docs-tool selector (type:/ns:/match:/changed/all) or a plain-English theme the agent resolves (e.g. text, image filters)." required: false - default: "group:text" + default: "text" type: string # -- Custom jobs ------------------------------------------------------- @@ -168,11 +168,12 @@ pre-agent-steps: # 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. + # validates inputs against the default branch). It can be a docs-tool selector + # (type:/ns:/match:/changed/all) or a plain-English theme the agent resolves; the + # daily default is the high-value `text` theme. Use `changed` to review just-touched docs. - name: Record review scope env: - REVIEW_SCOPE: ${{ inputs.review_scope || 'group:text' }} + REVIEW_SCOPE: ${{ inputs.review_scope || 'text' }} run: | printf '%s' "$REVIEW_SCOPE" > review-scope.txt echo "Review scope (existing docs): $(cat review-scope.txt)" @@ -261,29 +262,41 @@ A4. **Fix CRITICAL findings yourself** by editing the XML directly. Skip MINOR/s ### 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. +The review scope is in `review-scope.txt` at the workspace root — either a docs-tool selector +(`type:`/`ns:`/`match:`/`changed`/`all`) or a plain-English theme. 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): +R1. **Resolve the review scope into a concrete file list.** ```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 .. ``` - Shard >40 files into batches and process them one at a time; the text/font slice is ~16 files (one batch). - -R2. **Lint** the scope (deterministic, no model): + - If `$SCOPE` is a **docs-tool selector** (`file:` / `type:` / `ns:` / `match:` / `new` / `changed` / `all`), + resolve it directly (fuzzy `match:` needs `-Confirm:$false` in CI): + ```bash + 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 .. + ``` + - Otherwise `$SCOPE` is a **plain-English theme** (e.g. `text`). There is no curated group — resolve `all`, + then select the files whose type/namespace fits the theme yourself (see `references/scope-resolution.md`). + For `text` that is the `SKFont*` / `SKTextBlob*` / `SKFontMetrics` / `SKPaint` / `SKCanvas` text APIs + (~16 files, one batch). + + Shard >40 files into batches and process them one at a time. + +R2. **Lint** the resolved files (deterministic, no model). For a selector, lint it directly; for a theme, + lint each chosen file via a `file:` selector: ```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 .. + pwsh .agents/skills/api-docs/scripts/docs-tool.ps1 lint "$SELECTOR" && cd .. ``` + (where `$SELECTOR` is `$SCOPE` for a selector, or `file:` for each theme-selected file). R3. **Review, yourself.** For each file in the resolved list, follow `references/reviewing.md`: read the C# source first, then run the factual / example / quality checks against the `` blocks. Collect the linter output plus your findings into one deduped list. R4. **Fix (gated) yourself** by editing the XML directly. Priority order: (a) all **CRITICAL** findings, - (b) **obsolete-in-example** findings (the text/font slice has legacy `paint.TextSize` / `TextAlign` / + (b) **obsolete-in-example** findings (text APIs often carry 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:** timebox fixing to ~10 minutes — then stop, validate what you have, and From 5fe96e21678e86eb5a59e3ee888b8b6a0c982ac5 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Mon, 29 Jun 2026 22:33:24 +0200 Subject: [PATCH 13/21] auto-api-docs-writer: align review scope with the inventory-only tool The skill dropped the selector grammar (type:/ns:/match:); docs-tool.ps1 now only lists all/new/changed/file:PATH and the model maps a plain-English theme to files itself. Update Pass R to branch on inventory-mode-vs-theme, drop the obsolete -Confirm:$false fuzzy flag, and refresh the input/comment wording. Recompiled the lock. 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 | 35 +++++++++---------- 2 files changed, 33 insertions(+), 34 deletions(-) diff --git a/.github/workflows/auto-api-docs-writer.lock.yml b/.github/workflows/auto-api-docs-writer.lock.yml index 8049fb9..a475683 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":"124cf94364be81a8a4645caf7e502ab7eb63ab51fa7048aca0c00b5d24777bfe","compiler_version":"v0.71.5","strict":true,"agent_id":"copilot","agent_model":"claude-opus-4.7"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"89ab98acd33bb0044ab070ee56126e1b82e98a53b912296ffa6d16b2c7bb16e8","compiler_version":"v0.71.5","strict":true,"agent_id":"copilot","agent_model":"claude-opus-4.7"} # 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"}]} # ___ _ _ # / _ \ | | (_) @@ -80,7 +80,7 @@ name: "Auto API Docs Writer" type: string review_scope: default: text - description: "Review-pass scope: a docs-tool selector (type:/ns:/match:/changed/all) or a plain-English theme the agent resolves (e.g. text, image filters)." + description: "Review-pass scope: an inventory mode (all/new/changed/file:PATH) or a plain-English theme the agent resolves (e.g. text, image filters)." required: false type: string skiasharp_branch: @@ -216,23 +216,23 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_5f13ab03c8884c1c_EOF' + cat << 'GH_AW_PROMPT_377cd46c55898337_EOF' - GH_AW_PROMPT_5f13ab03c8884c1c_EOF + GH_AW_PROMPT_377cd46c55898337_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_5f13ab03c8884c1c_EOF' + cat << 'GH_AW_PROMPT_377cd46c55898337_EOF' Tools: create_pull_request, missing_tool, missing_data, noop - GH_AW_PROMPT_5f13ab03c8884c1c_EOF + GH_AW_PROMPT_377cd46c55898337_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" - cat << 'GH_AW_PROMPT_5f13ab03c8884c1c_EOF' + cat << 'GH_AW_PROMPT_377cd46c55898337_EOF' - GH_AW_PROMPT_5f13ab03c8884c1c_EOF + GH_AW_PROMPT_377cd46c55898337_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_5f13ab03c8884c1c_EOF' + cat << 'GH_AW_PROMPT_377cd46c55898337_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -264,12 +264,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_5f13ab03c8884c1c_EOF + GH_AW_PROMPT_377cd46c55898337_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_5f13ab03c8884c1c_EOF' + cat << 'GH_AW_PROMPT_377cd46c55898337_EOF' {{#runtime-import .github/workflows/auto-api-docs-writer.md}} - GH_AW_PROMPT_5f13ab03c8884c1c_EOF + GH_AW_PROMPT_377cd46c55898337_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -482,9 +482,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_0da9bd066c971f8e_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_0ede2173170c362d_EOF' {"create_pull_request":{"base_branch":"${{ inputs.docs_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_0da9bd066c971f8e_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_0ede2173170c362d_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -690,7 +690,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_4ba3666662db12d8_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_84f53362f1f00ea1_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -738,7 +738,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_4ba3666662db12d8_EOF + GH_AW_MCP_CONFIG_84f53362f1f00ea1_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 4091b46..b38daf0 100644 --- a/.github/workflows/auto-api-docs-writer.md +++ b/.github/workflows/auto-api-docs-writer.md @@ -31,7 +31,7 @@ on: default: "automation/write-api-docs" type: string review_scope: - description: "Review-pass scope: a docs-tool selector (type:/ns:/match:/changed/all) or a plain-English theme the agent resolves (e.g. text, image filters)." + description: "Review-pass scope: an inventory mode (all/new/changed/file:PATH) or a plain-English theme the agent resolves (e.g. text, image filters)." required: false default: "text" type: string @@ -168,8 +168,8 @@ pre-agent-steps: # 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). It can be a docs-tool selector - # (type:/ns:/match:/changed/all) or a plain-English theme the agent resolves; the + # validates inputs against the default branch). It can be an inventory mode + # (all/new/changed/file:PATH) or a plain-English theme the agent resolves; the # daily default is the high-value `text` theme. Use `changed` to review just-touched docs. - name: Record review scope env: @@ -262,34 +262,33 @@ A4. **Fix CRITICAL findings yourself** by editing the XML directly. Skip MINOR/s ### Pass R — Review existing docs (a scope, not just placeholders) -The review scope is in `review-scope.txt` at the workspace root — either a docs-tool selector -(`type:`/`ns:`/`match:`/`changed`/`all`) or a plain-English theme. This audits docs that are **already -filled**, which is where freshness/accuracy/example problems live. +The review scope is in `review-scope.txt` at the workspace root — either an inventory mode (`all` / +`new` / `changed` / `file:PATH`) or a plain-English theme. This audits docs that are **already filled**, +which is where freshness/accuracy/example problems live. -R1. **Resolve the review scope into a concrete file list.** +R1. **Turn the review scope into a concrete file list.** ```bash SCOPE="$(cat review-scope.txt)" ``` - - If `$SCOPE` is a **docs-tool selector** (`file:` / `type:` / `ns:` / `match:` / `new` / `changed` / `all`), - resolve it directly (fuzzy `match:` needs `-Confirm:$false` in CI): + - If `$SCOPE` is an **inventory mode** (`all` / `new` / `changed` / `file:PATH`), list it directly: ```bash 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 .. + pwsh .agents/skills/api-docs/scripts/docs-tool.ps1 resolve-scope "$SCOPE" && cd .. ``` - - Otherwise `$SCOPE` is a **plain-English theme** (e.g. `text`). There is no curated group — resolve `all`, - then select the files whose type/namespace fits the theme yourself (see `references/scope-resolution.md`). - For `text` that is the `SKFont*` / `SKTextBlob*` / `SKFontMetrics` / `SKPaint` / `SKCanvas` text APIs - (~16 files, one batch). + - Otherwise `$SCOPE` is a **plain-English theme** (e.g. `text`). There is no selector grammar — list + `all`, then select the files whose type/namespace fits the theme yourself (see + `references/scope-resolution.md`). For `text` that is the `SKFont*` / `SKTextBlob*` / `SKFontMetrics` / + `SKPaint` / `SKCanvas` text APIs (~16 files, one batch). Shard >40 files into batches and process them one at a time. -R2. **Lint** the resolved files (deterministic, no model). For a selector, lint it directly; for a theme, - lint each chosen file via a `file:` selector: +R2. **Lint** the resolved files (deterministic, no model). For an inventory mode, lint it directly; for a + theme, lint each chosen file via a `file:` path: ```bash cd skiasharp && DOCS_GIT_ROOT="$GITHUB_WORKSPACE" DOCS_DIR="$GITHUB_WORKSPACE/SkiaSharpAPI" \ - pwsh .agents/skills/api-docs/scripts/docs-tool.ps1 lint "$SELECTOR" && cd .. + pwsh .agents/skills/api-docs/scripts/docs-tool.ps1 lint "$TARGET" && cd .. ``` - (where `$SELECTOR` is `$SCOPE` for a selector, or `file:` for each theme-selected file). + (where `$TARGET` is `$SCOPE` for an inventory mode, or `file:` for each theme-selected file). R3. **Review, yourself.** For each file in the resolved list, follow `references/reviewing.md`: read the C# source first, then run the factual / example / quality checks against the `` blocks. Collect the From e164f7f7024537c6d7ee49f3ad23eb1682b2cbba Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Mon, 29 Jun 2026 22:42:51 +0200 Subject: [PATCH 14/21] auto-api-docs-writer: drop confusing 'no selector / no JSON' negatives State the procedure positively: a plain-English theme is resolved by listing all and selecting the fitting files; edits touch only . Recompiled lock. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/auto-api-docs-writer.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/auto-api-docs-writer.md b/.github/workflows/auto-api-docs-writer.md index b38daf0..6f0433f 100644 --- a/.github/workflows/auto-api-docs-writer.md +++ b/.github/workflows/auto-api-docs-writer.md @@ -275,10 +275,9 @@ R1. **Turn the review scope into a concrete file list.** cd skiasharp && DOCS_GIT_ROOT="$GITHUB_WORKSPACE" DOCS_DIR="$GITHUB_WORKSPACE/SkiaSharpAPI" \ pwsh .agents/skills/api-docs/scripts/docs-tool.ps1 resolve-scope "$SCOPE" && cd .. ``` - - Otherwise `$SCOPE` is a **plain-English theme** (e.g. `text`). There is no selector grammar — list - `all`, then select the files whose type/namespace fits the theme yourself (see - `references/scope-resolution.md`). For `text` that is the `SKFont*` / `SKTextBlob*` / `SKFontMetrics` / - `SKPaint` / `SKCanvas` text APIs (~16 files, one batch). + - Otherwise `$SCOPE` is a **plain-English theme** (e.g. `text`). List `all`, then select the files whose + type/namespace fits the theme yourself (see `references/scope-resolution.md`). For `text` that is the + `SKFont*` / `SKTextBlob*` / `SKFontMetrics` / `SKPaint` / `SKCanvas` text APIs (~16 files, one batch). Shard >40 files into batches and process them one at a time. @@ -338,7 +337,7 @@ Findings summary to stdout first. ## Critical rules -- **Edit the mdoc XML directly.** There is no JSON round-trip. Touch only `` content — never +- **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 ``. - **The validate gate (step V) MUST pass before the PR.** If you skip it, a malformed or surface-changing edit can ship. From 0ae2d3f87dc11a90620eb461357824896af16a7b Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Mon, 29 Jun 2026 22:47:22 +0200 Subject: [PATCH 15/21] auto-api-docs-writer: drop scope-resolution.md reference (file removed) The skill deleted references/scope-resolution.md; remove the file-list bullet and the Pass R parenthetical that pointed at it, plus one more 'no JSON step' negative. Recompiled the lock. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/auto-api-docs-writer.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/auto-api-docs-writer.md b/.github/workflows/auto-api-docs-writer.md index 6f0433f..07cdee3 100644 --- a/.github/workflows/auto-api-docs-writer.md +++ b/.github/workflows/auto-api-docs-writer.md @@ -218,12 +218,11 @@ first, then drive the phases below: - `skiasharp/.agents/skills/api-docs/SKILL.md` — the router. - `skiasharp/.agents/skills/api-docs/references/adding.md` — the direct-XML add procedure (pass A). - `skiasharp/.agents/skills/api-docs/references/reviewing.md` — the review procedure + checks (pass R). -- `skiasharp/.agents/skills/api-docs/references/scope-resolution.md` — how a scope selector resolves to files. - `skiasharp/.agents/skills/api-docs/references/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** — you read -and **edit the mdoc XML directly**; safety comes from the structural validator, not a merge guard. +`SkiaSharpAPI/` as uncommitted working-tree changes. You read and **edit the mdoc XML directly**; safety +comes from the structural validator, not a merge guard. ## Scope environment @@ -276,8 +275,8 @@ R1. **Turn the review scope into a concrete file list.** pwsh .agents/skills/api-docs/scripts/docs-tool.ps1 resolve-scope "$SCOPE" && cd .. ``` - Otherwise `$SCOPE` is a **plain-English theme** (e.g. `text`). List `all`, then select the files whose - type/namespace fits the theme yourself (see `references/scope-resolution.md`). For `text` that is the - `SKFont*` / `SKTextBlob*` / `SKFontMetrics` / `SKPaint` / `SKCanvas` text APIs (~16 files, one batch). + type/namespace fits the theme yourself. For `text` that is the `SKFont*` / `SKTextBlob*` / + `SKFontMetrics` / `SKPaint` / `SKCanvas` text APIs (~16 files, one batch). Shard >40 files into batches and process them one at a time. From e10f6fae441bec9222397fc5eb0f296fa5e12b0f Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Mon, 29 Jun 2026 23:12:22 +0200 Subject: [PATCH 16/21] auto-api-docs-writer: call docs QA via dotnet cake instead of pwsh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The deterministic docs gates (resolve-scope / lint / validate) moved out of the skill's PowerShell script (docs-tool.ps1) into Cake targets in the SkiaSharp clone: docs-resolve-scope, docs-lint, docs-validate. Update Pass A/R/V and the scope-environment note to invoke `dotnet cake --target=docs-* --scope=...`. The DOCS_GIT_ROOT/DOCS_DIR env overrides are unchanged — the Cake targets read the same vars, so the inverted-checkout sandbox keeps working (docs as primary checkout, SkiaSharp clone for binding/ source lookups). The compiled .lock.yml is unchanged: the body is imported at runtime and no frontmatter changed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/auto-api-docs-writer.md | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/.github/workflows/auto-api-docs-writer.md b/.github/workflows/auto-api-docs-writer.md index 07cdee3..0d3567c 100644 --- a/.github/workflows/auto-api-docs-writer.md +++ b/.github/workflows/auto-api-docs-writer.md @@ -226,11 +226,12 @@ comes from the structural validator, not a merge guard. ## 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): +The docs QA gates are Cake targets in the SkiaSharp clone (`scripts/infra/docs/docs.cake`), so by default +they 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 targets at it by exporting these +on every `dotnet cake --target=docs-*` call (they make `docs-resolve-scope`, `docs-lint`, and +`docs-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" @@ -243,7 +244,7 @@ DOCS_GIT_ROOT="$GITHUB_WORKSPACE" DOCS_DIR="$GITHUB_WORKSPACE/SkiaSharpAPI" A1. **Discover.** Resolve the placeholder files into an explicit list and shard it into ~25–40-file batches. ```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 .. + dotnet cake --target=docs-resolve-scope --scope=new && cd .. ``` A2. **Write (per batch), yourself.** Following `references/adding.md`, for each file read the C# source first, @@ -256,8 +257,8 @@ A3. **Review the written batch.** Run the deterministic linter, then review the A4. **Fix CRITICAL findings yourself** 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. +> If `docs-resolve-scope --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) @@ -272,7 +273,7 @@ R1. **Turn the review scope into a concrete file list.** - If `$SCOPE` is an **inventory mode** (`all` / `new` / `changed` / `file:PATH`), list it directly: ```bash cd skiasharp && DOCS_GIT_ROOT="$GITHUB_WORKSPACE" DOCS_DIR="$GITHUB_WORKSPACE/SkiaSharpAPI" \ - pwsh .agents/skills/api-docs/scripts/docs-tool.ps1 resolve-scope "$SCOPE" && cd .. + dotnet cake --target=docs-resolve-scope --scope="$SCOPE" && cd .. ``` - Otherwise `$SCOPE` is a **plain-English theme** (e.g. `text`). List `all`, then select the files whose type/namespace fits the theme yourself. For `text` that is the `SKFont*` / `SKTextBlob*` / @@ -284,7 +285,7 @@ R2. **Lint** the resolved files (deterministic, no model). For an inventory mode theme, lint each chosen file via a `file:` path: ```bash cd skiasharp && DOCS_GIT_ROOT="$GITHUB_WORKSPACE" DOCS_DIR="$GITHUB_WORKSPACE/SkiaSharpAPI" \ - pwsh .agents/skills/api-docs/scripts/docs-tool.ps1 lint "$TARGET" && cd .. + dotnet cake --target=docs-lint --scope="$TARGET" && cd .. ``` (where `$TARGET` is `$SCOPE` for an inventory mode, or `file:` for each theme-selected file). @@ -305,7 +306,7 @@ V. **Validate (replaces merge).** This gate makes direct editing safe — it mus 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 .. + dotnet cake --target=docs-validate --scope=new && cd .. ``` `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 From 1c78080c42a73e666f204913099ba78019f76453 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Tue, 30 Jun 2026 00:00:04 +0200 Subject: [PATCH 17/21] auto-api-docs-writer: match docs-format-docs single format+check target The api-docs cake tooling folded its three QA tasks (docs-resolve-scope / docs-lint / docs-validate) into the existing docs-format-docs target, which now formats every doc and runs the deterministic checks in one pass (warnings for missing/quality issues, build-failing errors for broken XML/CDATA). The DOCS_GIT_ROOT/DOCS_DIR env overrides are gone too. Update the agent prompt to match: - Drop the "Scope environment" env-injection section; the docs are the primary checkout at the workspace root and skiasharp/docs/SkiaSharpAPI is symlinked to them, so the post-step docs-format-docs gate sees the edits with no override. - Discover files with plain git/find instead of docs-resolve-scope (placeholders via `git status` + `grep 'To be added.'`; review scope via `git status`/`find`/theme selection). - Replace the docs-lint step with an optional docs-format-docs run for the deterministic findings. - Replace the docs-validate step (V): the post-step docs-format-docs is now the gate and fails the run on malformed-xml/broken-cdata, so the agent no longer runs a validate target. - Refresh the Critical rules accordingly (post-step gate, no structural validator, signatures owned by mdoc). Body-only change; the compiled .lock.yml imports the prompt at runtime and its only cake call (the docs-format-docs post-step) is unchanged, so no recompile is needed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/auto-api-docs-writer.md | 100 ++++++++++------------ 1 file changed, 47 insertions(+), 53 deletions(-) diff --git a/.github/workflows/auto-api-docs-writer.md b/.github/workflows/auto-api-docs-writer.md index 0d3567c..c9ba14e 100644 --- a/.github/workflows/auto-api-docs-writer.md +++ b/.github/workflows/auto-api-docs-writer.md @@ -222,29 +222,27 @@ first, then drive the phases below: The stub regeneration (mdoc) already ran as a pre-step, so the placeholder `*.xml` files are present in `SkiaSharpAPI/` as uncommitted working-tree changes. You read and **edit the mdoc XML directly**; safety -comes from the structural validator, not a merge guard. +comes from the post-step `docs-format-docs` gate (it fails the run on broken XML), not a merge guard. -## Scope environment +## Where the files are -The docs QA gates are Cake targets in the SkiaSharp clone (`scripts/infra/docs/docs.cake`), so by default -they 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 targets at it by exporting these -on every `dotnet cake --target=docs-*` call (they make `docs-resolve-scope`, `docs-lint`, and -`docs-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 docs repo is the **primary checkout** at the workspace root; the regenerated and edited mdoc XML lives +under `SkiaSharpAPI/`. The SkiaSharp clone (cake scripts + skill + `binding/` source) is at `skiasharp/`, +and `skiasharp/docs/SkiaSharpAPI` is symlinked to the workspace `SkiaSharpAPI/`, so the post-step +`docs-format-docs` gate checks your edits directly. Discover files with plain `git`/`find` against the +workspace checkout — there is no scope/lint cake target to call. ## Execution order ### Pass A — Add (fill placeholders) -A1. **Discover.** Resolve the placeholder files into an explicit list and shard it into ~25–40-file batches. +A1. **Discover.** The regenerated stubs are uncommitted working-tree changes. List the changed type docs + that still hold a `To be added.` placeholder (excluding generated files), and shard into ~25–40-file + batches: ```bash - cd skiasharp && DOCS_GIT_ROOT="$GITHUB_WORKSPACE" DOCS_DIR="$GITHUB_WORKSPACE/SkiaSharpAPI" \ - dotnet cake --target=docs-resolve-scope --scope=new && cd .. + git status --porcelain -- SkiaSharpAPI/ | awk '{print $2}' \ + | grep -E '\.xml$' | grep -vE '(^|/)(index|_filter|ns-[^/]*)\.xml$|/FrameworksIndex/' \ + | xargs -r grep -l 'To be added\.' ``` A2. **Write (per batch), yourself.** Following `references/adding.md`, for each file read the C# source first, @@ -252,13 +250,15 @@ A2. **Write (per batch), yourself.** Following `references/adding.md`, for each certainty keeps its placeholder (note it as a `DEFERRED` line) so the next run re-detects it. Process the batches one at a time so each stays in working memory. -A3. **Review the written batch.** Run the deterministic linter, then review the batch's files against the - checks in `references/reviewing.md` and note the findings. +A3. **Review the written batch.** Review the batch's files against the checks in `references/reviewing.md` + and note the findings. To surface the deterministic findings (`obsolete-in-example`, accessor-verb, + spelling, …) you may run `cd skiasharp && dotnet cake --target=docs-format-docs && cd ..` — it prints + `[docs] | …` lines and also formats (harmless, idempotent). A4. **Fix CRITICAL findings yourself** by editing the XML directly. Skip MINOR/style for the automated run. -> If `docs-resolve-scope --scope=new` returns **no** placeholder files (the common case once docs are filled), -> pass A is a no-op — skip straight to pass R. +> If no changed `*.xml` still contains `To be added.` (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) @@ -266,51 +266,42 @@ The review scope is in `review-scope.txt` at the workspace root — either an in `new` / `changed` / `file:PATH`) or a plain-English theme. This audits docs that are **already filled**, which is where freshness/accuracy/example problems live. -R1. **Turn the review scope into a concrete file list.** +R1. **Turn the review scope into a concrete file list** with plain `git`/`find` against the workspace docs + (always drop generated files: `index.xml`, `ns-*.xml`, `_filter.xml`, `FrameworksIndex/`). ```bash SCOPE="$(cat review-scope.txt)" ``` - - If `$SCOPE` is an **inventory mode** (`all` / `new` / `changed` / `file:PATH`), list it directly: - ```bash - cd skiasharp && DOCS_GIT_ROOT="$GITHUB_WORKSPACE" DOCS_DIR="$GITHUB_WORKSPACE/SkiaSharpAPI" \ - dotnet cake --target=docs-resolve-scope --scope="$SCOPE" && cd .. - ``` + - `changed` / `new`: the working-tree diff — `git status --porcelain -- SkiaSharpAPI/ | awk '{print $2}'`. + - `all`: every non-generated type doc — `find SkiaSharpAPI -name '*.xml'`. + - `file:PATH`: that one path. - Otherwise `$SCOPE` is a **plain-English theme** (e.g. `text`). List `all`, then select the files whose type/namespace fits the theme yourself. For `text` that is the `SKFont*` / `SKTextBlob*` / `SKFontMetrics` / `SKPaint` / `SKCanvas` text APIs (~16 files, one batch). Shard >40 files into batches and process them one at a time. -R2. **Lint** the resolved files (deterministic, no model). For an inventory mode, lint it directly; for a - theme, lint each chosen file via a `file:` path: - ```bash - cd skiasharp && DOCS_GIT_ROOT="$GITHUB_WORKSPACE" DOCS_DIR="$GITHUB_WORKSPACE/SkiaSharpAPI" \ - dotnet cake --target=docs-lint --scope="$TARGET" && cd .. - ``` - (where `$TARGET` is `$SCOPE` for an inventory mode, or `file:` for each theme-selected file). +R2. **(Optional) Surface deterministic findings.** Run `cd skiasharp && dotnet cake --target=docs-format-docs + && cd ..` to print the `[docs] | …` findings (obsolete-in-example, accessor-verb, spelling, + repeated word, …) across the tree. This is the same deterministic check the post-step gate runs; it also + formats (harmless). Fold its output into your own review below. R3. **Review, yourself.** For each file in the resolved list, follow `references/reviewing.md`: read the C# source first, then run the factual / example / quality checks against the `` blocks. Collect the - linter output plus your findings into one deduped list. + deterministic findings plus your own into one deduped list. R4. **Fix (gated) yourself** by editing the XML directly. Priority order: (a) all **CRITICAL** findings, (b) **obsolete-in-example** findings (text APIs often carry 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:** timebox fixing to ~10 minutes — then stop, validate what you have, and - open the PR. A smaller validated PR beats none. + `SKShader` quality bar. **Budget:** timebox fixing to ~10 minutes — then stop and open the PR. A smaller + PR beats none. ### 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" \ - dotnet cake --target=docs-validate --scope=new && cd .. - ``` - `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. +V. **The validation gate is automatic.** The host post-step runs `docs-format-docs`, which formats every doc + and **fails the run on broken XML/CDATA** (`malformed-xml` / `broken-cdata`) — so a malformed edit can + never ship a PR. You do **not** run it as your final step. Just make sure every file you touched stays + well-formed and you changed only `` content (signatures are owned by mdoc regeneration). C. **Commit and PR.** If any pass produced edits, **commit on the branch you are already on** — the host prepared a dedicated throwaway PR branch for you before you started (it is **not** the dispatch ref). Do @@ -332,26 +323,29 @@ C. **Commit and PR.** If any pass produced edits, **commit on the branch you are 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 -Findings summary to stdout first. +If there are no documentation changes, call the `noop` tool instead — but still print the Findings summary +to stdout first. ## 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 ``. -- **The validate gate (step V) MUST pass before the PR.** If you skip it, a malformed or surface-changing edit can ship. -- **Do NOT run `docs-format-docs`** — formatting runs automatically as a post-step. + `FrameworksIndex/`). Signatures are owned by mdoc regeneration and are visible in the PR diff. +- **The post-step `docs-format-docs` gate MUST pass.** Any `malformed-xml` / `broken-cdata` error fails the + run and blocks the PR, so keep every edit well-formed. +- **You need not run `docs-format-docs` yourself** — formatting and the deterministic checks run automatically + as the post-step. You *may* run it mid-pass to read its findings (it is idempotent), but never rely on it as + your final 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. - **Do everything yourself, in the foreground.** Do **not** launch sub-agents — discovery, writing, review, - fixing, validation, committing, and PR creation are all your own work. The gh-aw sandbox does not honor + fixing, committing, and PR creation are all your own work. The gh-aw sandbox does not honor per-sub-agent models, and backgrounding a terminal agent and then ending your turn before the PR is created 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. **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. +- **Budget awareness:** Prioritize reaching the PR. Pass A (add) is usually a no-op now, so spend the budget + on pass R. **Timebox your fixing to ~10 minutes; if you exceed it, stop fixing and open the PR.** A smaller + PR beats none — the post-step gate still validates whatever you ship. - **Always commit on the branch you are already on** (the host prepared a dedicated throwaway PR branch before you started) and **never `git checkout` 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 From 58511f5df8ce9d6731c2b3cb25c08d5e30894f61 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Tue, 30 Jun 2026 00:32:25 +0200 Subject: [PATCH 18/21] auto-api-docs-writer: drop malformed-xml from the gate description MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit docs-format-docs no longer emits a malformed-xml finding — a file that will not parse now throws from XDocument.Load, and broken-cdata remains the only build-failing lint class. Update the validation-gate prose to match. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/auto-api-docs-writer.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/auto-api-docs-writer.md b/.github/workflows/auto-api-docs-writer.md index c9ba14e..9d2b738 100644 --- a/.github/workflows/auto-api-docs-writer.md +++ b/.github/workflows/auto-api-docs-writer.md @@ -299,9 +299,10 @@ R4. **Fix (gated) yourself** by editing the XML directly. Priority order: (a) al ### Finalize V. **The validation gate is automatic.** The host post-step runs `docs-format-docs`, which formats every doc - and **fails the run on broken XML/CDATA** (`malformed-xml` / `broken-cdata`) — so a malformed edit can - never ship a PR. You do **not** run it as your final step. Just make sure every file you touched stays - well-formed and you changed only `` content (signatures are owned by mdoc regeneration). + and **fails the run on broken XML** — unparseable XML throws when it is loaded, and a destroyed CDATA block + logs a `broken-cdata` error — so a malformed edit can never ship a PR. You do **not** run it as your final + step. Just make sure every file you touched stays well-formed and you changed only `` content + (signatures are owned by mdoc regeneration). C. **Commit and PR.** If any pass produced edits, **commit on the branch you are already on** — the host prepared a dedicated throwaway PR branch for you before you started (it is **not** the dispatch ref). Do @@ -331,7 +332,7 @@ to stdout first. - **Edit the mdoc XML directly.** Touch only `` content — never `MemberSignature`/`TypeSignature`, attributes, or generated files (`index.xml`, `ns-*.xml`, `_filter.xml`, `FrameworksIndex/`). Signatures are owned by mdoc regeneration and are visible in the PR diff. -- **The post-step `docs-format-docs` gate MUST pass.** Any `malformed-xml` / `broken-cdata` error fails the +- **The post-step `docs-format-docs` gate MUST pass.** Unparseable XML or a `broken-cdata` error fails the run and blocks the PR, so keep every edit well-formed. - **You need not run `docs-format-docs` yourself** — formatting and the deterministic checks run automatically as the post-step. You *may* run it mid-pass to read its findings (it is idempotent), but never rely on it as From 76b85434d841fe482df259ca5a7ce5e162d038aa Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Tue, 30 Jun 2026 02:11:09 +0200 Subject: [PATCH 19/21] Slim workflow to a thin trigger; fix branch checkout and drop dead inputs The workflow restated the skill's whole procedure, ran a broken/hidden SkiaSharp build that produced no DLL, and never self-gated. Make the skill the single source of truth and carry only gh-aw-specific wiring here: - Pin the agent-job checkout to docs_base_branch (was the dispatch ref) so the working tree, regenerated stubs, and PR base all agree; dispatching on a feature branch no longer reviews the wrong base. - Remove the review_scope input and Record review scope step: the work set is self-selecting (format findings + new placeholders + files changed vs base). - Remove the continue-on-error Bootstrap build (externals-download + dotnet build): docs are XML and need no build; it silently produced no DLL. - Rewrite the body as a thin trigger that defers to the api-docs skill and encodes the loop: run docs-format-docs to collect the to-do list, do the work (fill placeholders + the three reviewers from references/reviewing.md), then run docs-format-docs again to validate before the PR. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../workflows/auto-api-docs-writer.lock.yml | 46 ++-- .github/workflows/auto-api-docs-writer.md | 236 +++++------------- 2 files changed, 75 insertions(+), 207 deletions(-) diff --git a/.github/workflows/auto-api-docs-writer.lock.yml b/.github/workflows/auto-api-docs-writer.lock.yml index a475683..7ff6fad 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":"89ab98acd33bb0044ab070ee56126e1b82e98a53b912296ffa6d16b2c7bb16e8","compiler_version":"v0.71.5","strict":true,"agent_id":"copilot","agent_model":"claude-opus-4.7"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"fa3adbde947270e02d26df8dea81f10d71e29720c661a87448e287930730dee1","compiler_version":"v0.71.5","strict":true,"agent_id":"copilot","agent_model":"claude-opus-4.7"} # 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"}]} # ___ _ _ # / _ \ | | (_) @@ -78,11 +78,6 @@ name: "Auto API Docs Writer" description: Throwaway PR head branch the agent commits to (force-recreated each run). required: false type: string - review_scope: - default: text - description: "Review-pass scope: an inventory mode (all/new/changed/file:PATH) or a plain-English theme the agent resolves (e.g. text, image filters)." - required: false - type: string skiasharp_branch: default: main description: SkiaSharp branch to use for scripts and references @@ -216,23 +211,23 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_377cd46c55898337_EOF' + cat << 'GH_AW_PROMPT_b71f6efc37a3e9aa_EOF' - GH_AW_PROMPT_377cd46c55898337_EOF + GH_AW_PROMPT_b71f6efc37a3e9aa_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_377cd46c55898337_EOF' + cat << 'GH_AW_PROMPT_b71f6efc37a3e9aa_EOF' Tools: create_pull_request, missing_tool, missing_data, noop - GH_AW_PROMPT_377cd46c55898337_EOF + GH_AW_PROMPT_b71f6efc37a3e9aa_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" - cat << 'GH_AW_PROMPT_377cd46c55898337_EOF' + cat << 'GH_AW_PROMPT_b71f6efc37a3e9aa_EOF' - GH_AW_PROMPT_377cd46c55898337_EOF + GH_AW_PROMPT_b71f6efc37a3e9aa_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_377cd46c55898337_EOF' + cat << 'GH_AW_PROMPT_b71f6efc37a3e9aa_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -264,12 +259,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_377cd46c55898337_EOF + GH_AW_PROMPT_b71f6efc37a3e9aa_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_377cd46c55898337_EOF' + cat << 'GH_AW_PROMPT_b71f6efc37a3e9aa_EOF' {{#runtime-import .github/workflows/auto-api-docs-writer.md}} - GH_AW_PROMPT_377cd46c55898337_EOF + GH_AW_PROMPT_b71f6efc37a3e9aa_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -393,6 +388,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false + ref: ${{ inputs.docs_base_branch || 'main' }} fetch-depth: 1 - name: Create gh-aw temp directory run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" @@ -460,20 +456,10 @@ jobs: with: name: docs-regenerated path: SkiaSharpAPI/ - - env: - REVIEW_SCOPE: ${{ inputs.review_scope || 'text' }} - name: Record review scope - run: "printf '%s' \"$REVIEW_SCOPE\" > 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) 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 @@ -482,9 +468,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_0ede2173170c362d_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_6d6246d5000019fb_EOF' {"create_pull_request":{"base_branch":"${{ inputs.docs_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_0ede2173170c362d_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_6d6246d5000019fb_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -690,7 +676,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_84f53362f1f00ea1_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_cf9b0e53c449db24_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -738,7 +724,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_84f53362f1f00ea1_EOF + GH_AW_MCP_CONFIG_cf9b0e53c449db24_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 9d2b738..e2cb9a4 100644 --- a/.github/workflows/auto-api-docs-writer.md +++ b/.github/workflows/auto-api-docs-writer.md @@ -30,11 +30,6 @@ on: required: false default: "automation/write-api-docs" type: string - review_scope: - description: "Review-pass scope: an inventory mode (all/new/changed/file:PATH) or a plain-English theme the agent resolves (e.g. text, image filters)." - required: false - default: "text" - type: string # -- Custom jobs ------------------------------------------------------- # Stub regeneration runs mdoc to produce the XML reference stubs. mdoc.exe is a @@ -96,9 +91,14 @@ jobs: retention-days: 1 # -- Checkout ---------------------------------------------------------- -# Primary: this docs repo only. SkiaSharp is cloned in pre-agent-steps. +# Primary: this docs repo only, pinned to docs_base_branch — NOT the dispatch ref. +# The stubs are regenerated against docs_base_branch and the PR targets it too, so +# checking the working tree out at the same ref keeps all three in agreement; without +# this pin, dispatching on a feature branch would review the wrong base and produce a +# polluted diff. SkiaSharp is cloned separately in pre-agent-steps. checkout: - fetch-depth: 1 + ref: ${{ inputs.docs_base_branch || 'main' }} timeout-minutes: 120 concurrency: group: auto-api-docs-writer @@ -144,14 +144,13 @@ 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. The branch name is the `docs_head_branch` input - # (default automation/write-api-docs). This is the host-side guarantee that backs - # up the "commit on the branch you're already on" rule in the prompt. + # The primary checkout already put the working tree on docs_base_branch (see the + # checkout block). gh-aw leaves that on the dispatch ref's branch name, and the + # agent will commit there; safe-outputs (recreate_ref) then force-overwrites that + # ref. If that branch were the workflow's own source (a dev/* branch under review), + # the force-push would destroy it. So move onto a throwaway head branch up-front — + # every commit and the PR head land there, never on the dispatch ref. The base + # content is unchanged (still docs_base_branch); only the branch name changes. - name: Use a dedicated PR branch (never the dispatch ref) env: DOCS_HEAD_BRANCH: ${{ inputs.docs_head_branch || 'automation/write-api-docs' }} @@ -165,19 +164,6 @@ 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). It can be an inventory mode - # (all/new/changed/file:PATH) or a plain-English theme the agent resolves; the - # daily default is the high-value `text` theme. Use `changed` to review just-touched docs. - - name: Record review scope - env: - REVIEW_SCOPE: ${{ inputs.review_scope || 'text' }} - run: | - printf '%s' "$REVIEW_SCOPE" > 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' }} @@ -189,17 +175,6 @@ pre-agent-steps: ln -sfn "$(pwd)/SkiaSharpAPI" skiasharp/docs/SkiaSharpAPI cd skiasharp && dotnet tool restore - # 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: | - 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. @@ -210,159 +185,66 @@ post-steps: # Auto API Docs Writer -You are the agent 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. You do the whole job yourself — there is **no sub-agent fan-out**. Read these -first, then drive the phases below: - -- `skiasharp/.agents/skills/api-docs/SKILL.md` — the router. -- `skiasharp/.agents/skills/api-docs/references/adding.md` — the direct-XML add procedure (pass A). -- `skiasharp/.agents/skills/api-docs/references/reviewing.md` — the review procedure + checks (pass R). -- `skiasharp/.agents/skills/api-docs/references/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. You read and **edit the mdoc XML directly**; safety -comes from the post-step `docs-format-docs` gate (it fails the run on broken XML), not a merge guard. - -## Where the files are - -The docs repo is the **primary checkout** at the workspace root; the regenerated and edited mdoc XML lives -under `SkiaSharpAPI/`. The SkiaSharp clone (cake scripts + skill + `binding/` source) is at `skiasharp/`, -and `skiasharp/docs/SkiaSharpAPI` is symlinked to the workspace `SkiaSharpAPI/`, so the post-step -`docs-format-docs` gate checks your edits directly. Discover files with plain `git`/`find` against the -workspace checkout — there is no scope/lint cake target to call. - -## Execution order - -### Pass A — Add (fill placeholders) - -A1. **Discover.** The regenerated stubs are uncommitted working-tree changes. List the changed type docs - that still hold a `To be added.` placeholder (excluding generated files), and shard into ~25–40-file - batches: - ```bash - git status --porcelain -- SkiaSharpAPI/ | awk '{print $2}' \ - | grep -E '\.xml$' | grep -vE '(^|/)(index|_filter|ns-[^/]*)\.xml$|/FrameworksIndex/' \ - | xargs -r grep -l 'To be added\.' - ``` - -A2. **Write (per batch), yourself.** Following `references/adding.md`, for each file read the C# source first, - then fill only the empty/`To be added.` fields and edit the XML in place. A type you cannot document with - certainty keeps its placeholder (note it as a `DEFERRED` line) so the next run re-detects it. Process the - batches one at a time so each stays in working memory. - -A3. **Review the written batch.** Review the batch's files against the checks in `references/reviewing.md` - and note the findings. To surface the deterministic findings (`obsolete-in-example`, accessor-verb, - spelling, …) you may run `cd skiasharp && dotnet cake --target=docs-format-docs && cd ..` — it prints - `[docs] | …` lines and also formats (harmless, idempotent). - -A4. **Fix CRITICAL findings yourself** by editing the XML directly. Skip MINOR/style for the automated run. +This workflow is a **trigger** for the SkiaSharp **api-docs** skill, run daily as a two-pass pipeline. +**Load the skill and let it drive** — it is the single source of truth for *how* to add and review docs. +Do the whole job yourself in the foreground; do **not** launch sub-agents. -> If no changed `*.xml` still contains `To be added.` (the common case once docs are filled), pass A is a -> no-op — skip straight to pass R. +**Read first:** `skiasharp/.agents/skills/api-docs/SKILL.md` (the router). It points to +`references/adding.md` (add pass), `references/reviewing.md` (review pass), and the fact tables. Follow +those procedures — everything below is only the run-specific wiring the skill does not cover. -### Pass R — Review existing docs (a scope, not just placeholders) +## This run: format → work → format -The review scope is in `review-scope.txt` at the workspace root — either an inventory mode (`all` / -`new` / `changed` / `file:PATH`) or a plain-English theme. This audits docs that are **already filled**, -which is where freshness/accuracy/example problems live. +The deterministic gate `docs-format-docs` is **also your work-finder**: it lints every type file and prints a +`[docs] | file | docId | message` line per fixable defect. Run it **first** to collect the to-do +list, **work** the files, then run it **again** to validate. It is **only the lint layer** — the real +correctness work is the three reviewers in `references/reviewing.md`, which it cannot do. -R1. **Turn the review scope into a concrete file list** with plain `git`/`find` against the workspace docs - (always drop generated files: `index.xml`, `ns-*.xml`, `_filter.xml`, `FrameworksIndex/`). - ```bash - SCOPE="$(cat review-scope.txt)" - ``` - - `changed` / `new`: the working-tree diff — `git status --porcelain -- SkiaSharpAPI/ | awk '{print $2}'`. - - `all`: every non-generated type doc — `find SkiaSharpAPI -name '*.xml'`. - - `file:PATH`: that one path. - - Otherwise `$SCOPE` is a **plain-English theme** (e.g. `text`). List `all`, then select the files whose - type/namespace fits the theme yourself. For `text` that is the `SKFont*` / `SKTextBlob*` / - `SKFontMetrics` / `SKPaint` / `SKCanvas` text APIs (~16 files, one batch). +1. **Collect.** `cd skiasharp && dotnet cake --target=docs-format-docs && cd ..` — capture its `[docs]` + findings (obsolete-in-example, accessor-verb, spelling, repeated-word, …). Those, plus any newly-introduced + `To be added.` placeholders (uncommitted stub changes under `SkiaSharpAPI/`) and the files changed vs the + base, are your work set. The run also reformats in place (idempotent, harmless). - Shard >40 files into batches and process them one at a time. +2. **Work, source-first, per the skill.** For each file in the set: + - **Add** — fill `To be added.` placeholders per `references/adding.md` (read the C# source first). + - **Review** — run all three correctness reviewers from `references/reviewing.md`: **A. Factual** (claims + vs source, cite `path:line`), **B. Examples** (every snippet compiles, real APIs, **no obsolete + members**), **C. Quality** (.NET conventions, completeness, style). The deterministic findings only seed + this — the factual/example/quality errors are yours to find and are where the real problems hide. + - **Fix** CRITICAL findings (and every obsolete-in-example) by editing the XML directly; where a central + type is example-poor, add one correct, non-obsolete example. Touch only `` content. + Work in batches of ~25–40 files. **Timebox fixing to ~10 minutes**, then stop — a smaller PR beats none. -R2. **(Optional) Surface deterministic findings.** Run `cd skiasharp && dotnet cake --target=docs-format-docs - && cd ..` to print the `[docs] | …` findings (obsolete-in-example, accessor-verb, spelling, - repeated word, …) across the tree. This is the same deterministic check the post-step gate runs; it also - formats (harmless). Fold its output into your own review below. +3. **Validate.** Re-run `cd skiasharp && dotnet cake --target=docs-format-docs && cd ..` and fix anything it + still reports. It **fails the run on broken XML/CDATA**, so every file you touched must stay well-formed. + (The host re-runs it after you as a backstop, but you own getting it green.) -R3. **Review, yourself.** For each file in the resolved list, follow `references/reviewing.md`: read the C# - source first, then run the factual / example / quality checks against the `` blocks. Collect the - deterministic findings plus your own into one deduped list. +## Paths in this workflow -R4. **Fix (gated) yourself** by editing the XML directly. Priority order: (a) all **CRITICAL** findings, - (b) **obsolete-in-example** findings (text APIs often carry 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:** timebox fixing to ~10 minutes — then stop and open the PR. A smaller - PR beats none. +The **docs repo is the workspace root**; the SkiaSharp clone (skill, cake scripts, `binding/` source) is at +`skiasharp/`, with `skiasharp/docs/SkiaSharpAPI` symlinked to the workspace `SkiaSharpAPI/`. Translate +SKILL.md paths accordingly: -### Finalize +| SKILL.md reference | Here | +|---|---| +| `docs/SkiaSharpAPI/` | `SkiaSharpAPI/` (workspace root) | +| `.agents/skills/api-docs/` | `skiasharp/.agents/skills/api-docs/` | +| `binding/SkiaSharp/`, `binding/HarfBuzzSharp/` | `skiasharp/binding/...` | -V. **The validation gate is automatic.** The host post-step runs `docs-format-docs`, which formats every doc - and **fails the run on broken XML** — unparseable XML throws when it is loaded, and a destroyed CDATA block - logs a `broken-cdata` error — so a malformed edit can never ship a PR. You do **not** run it as your final - step. Just make sure every file you touched stays well-formed and you changed only `` content - (signatures are owned by mdoc regeneration). +## Commit and open the PR -C. **Commit and PR.** If any pass produced edits, **commit on the branch you are already on** — the host - prepared a dedicated throwaway PR branch for you before you started (it is **not** the dispatch ref). Do - **not** switch or create another branch. Stage **only** hand-edited type docs under `SkiaSharpAPI/`, and - **unstage the generated files** (stub regeneration rewrites them; they must never appear in the PR): +1. **Commit on the branch you are already on** — the host prepared a dedicated throwaway PR branch before you + started; it is **not** the dispatch ref. Do **not** `git checkout` or create another branch: safe-outputs + force-overwrites the branch you commit on, so committing on the dispatch ref would destroy the workflow + source. Stage only hand-edited type docs and drop the generated files: ```bash git add SkiaSharpAPI/ git reset -q -- SkiaSharpAPI/index.xml 'SkiaSharpAPI/ns-*.xml' SkiaSharpAPI/_filter.xml SkiaSharpAPI/FrameworksIndex/ git commit -m "Fill and review API documentation" ``` - **Never `git checkout` 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: the current working branch (leave the `create_pull_request` branch to the host default — do not - hardcode a name) - - Title: `Fill and review API documentation` - - 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. +2. **Open the PR** with the `create_pull_request` tool — title `Fill and review API documentation`; body: + what you filled (file count), what you reviewed (file count), a **Findings summary** (counts by severity + + the machine `FINDING |` block from `references/reviewing.md`), and what you fixed vs deferred. If there are + no changes, call `noop` instead — but print the Findings summary first. -If there are no documentation changes, call the `noop` tool instead — but still print the Findings summary -to stdout first. - -## Critical rules - -- **Edit the mdoc XML directly.** Touch only `` content — never - `MemberSignature`/`TypeSignature`, attributes, or generated files (`index.xml`, `ns-*.xml`, `_filter.xml`, - `FrameworksIndex/`). Signatures are owned by mdoc regeneration and are visible in the PR diff. -- **The post-step `docs-format-docs` gate MUST pass.** Unparseable XML or a `broken-cdata` error fails the - run and blocks the PR, so keep every edit well-formed. -- **You need not run `docs-format-docs` yourself** — formatting and the deterministic checks run automatically - as the post-step. You *may* run it mid-pass to read its findings (it is idempotent), but never rely on it as - your final 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. -- **Do everything yourself, in the foreground.** Do **not** launch sub-agents — discovery, writing, review, - fixing, committing, and PR creation are all your own work. The gh-aw sandbox does not honor - per-sub-agent models, and backgrounding a terminal agent and then ending your turn before the PR is created - kills the session with no PR. -- **Budget awareness:** Prioritize reaching the PR. Pass A (add) is usually a no-op now, so spend the budget - on pass R. **Timebox your fixing to ~10 minutes; if you exceed it, stop fixing and open the PR.** A smaller - PR beats none — the post-step gate still validates whatever you ship. -- **Always commit on the branch you are already on** (the host prepared a dedicated throwaway PR branch before - you started) and **never `git checkout` 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. Do not switch or create another branch - even when the current branch looks like 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`. - 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/` | +**COMPLETION GATE:** the run is not done until you have called `create_pull_request` or `noop`. From 2f8b34296dc16f9def86a641089d0df50acbbdb2 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Tue, 30 Jun 2026 02:52:23 +0200 Subject: [PATCH 20/21] Eliminate docs-tree duplication and quiet git in the agent run Two model reviews (GPT-5.5, Opus 4.7) of run 28411250198 found the agent burned ~5-10 tool calls + 3 extra format passes diagnosing a duplicated docs tree, and hit a stray interactive git pager. Both pre-emptable in the host wiring: - Clone SkiaSharp with --no-recurse-submodules. The docs format/lint pass only reads in-tree files, so submodules are dead weight; worse, --recurse-submodules checked the docs submodule out as a real dir at skiasharp/docs/SkiaSharpAPI, which the ln -sfn could not replace, nesting a second copy. The format glob (skiasharp/docs/**/*.xml) then walked both and double-counted (~404 -> ~812). - rm -rf the docs dir before symlinking, and assert the linked view has exactly the same XML count as the workspace (fail fast on duplication). - Disable the git pager / detached-HEAD advice and echo the SkiaSharp HEAD SHA. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../workflows/auto-api-docs-writer.lock.yml | 36 +++++++++---------- .github/workflows/auto-api-docs-writer.md | 28 +++++++++++++-- 2 files changed, 43 insertions(+), 21 deletions(-) diff --git a/.github/workflows/auto-api-docs-writer.lock.yml b/.github/workflows/auto-api-docs-writer.lock.yml index 7ff6fad..1133b34 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":"fa3adbde947270e02d26df8dea81f10d71e29720c661a87448e287930730dee1","compiler_version":"v0.71.5","strict":true,"agent_id":"copilot","agent_model":"claude-opus-4.7"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"8017948946549b70802c0b1514bdbf1bd3e9a0223e6310a60a36cf9ee8b1a655","compiler_version":"v0.71.5","strict":true,"agent_id":"copilot","agent_model":"claude-opus-4.7"} # 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"}]} # ___ _ _ # / _ \ | | (_) @@ -211,23 +211,23 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_b71f6efc37a3e9aa_EOF' + cat << 'GH_AW_PROMPT_5f7c8f1dc8448757_EOF' - GH_AW_PROMPT_b71f6efc37a3e9aa_EOF + GH_AW_PROMPT_5f7c8f1dc8448757_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_b71f6efc37a3e9aa_EOF' + cat << 'GH_AW_PROMPT_5f7c8f1dc8448757_EOF' Tools: create_pull_request, missing_tool, missing_data, noop - GH_AW_PROMPT_b71f6efc37a3e9aa_EOF + GH_AW_PROMPT_5f7c8f1dc8448757_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" - cat << 'GH_AW_PROMPT_b71f6efc37a3e9aa_EOF' + cat << 'GH_AW_PROMPT_5f7c8f1dc8448757_EOF' - GH_AW_PROMPT_b71f6efc37a3e9aa_EOF + GH_AW_PROMPT_5f7c8f1dc8448757_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_b71f6efc37a3e9aa_EOF' + cat << 'GH_AW_PROMPT_5f7c8f1dc8448757_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -259,12 +259,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_b71f6efc37a3e9aa_EOF + GH_AW_PROMPT_5f7c8f1dc8448757_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_b71f6efc37a3e9aa_EOF' + cat << 'GH_AW_PROMPT_5f7c8f1dc8448757_EOF' {{#runtime-import .github/workflows/auto-api-docs-writer.md}} - GH_AW_PROMPT_b71f6efc37a3e9aa_EOF + GH_AW_PROMPT_5f7c8f1dc8448757_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -450,7 +450,7 @@ jobs: - env: DOCS_HEAD_BRANCH: ${{ inputs.docs_head_branch || 'automation/write-api-docs' }} name: Use a dedicated PR branch (never the dispatch ref) - run: "git checkout -B \"$DOCS_HEAD_BRANCH\"\necho \"Working branch: $(git branch --show-current)\"\n" + run: "# Unattended runs must never block on an interactive git pager or a\n# detached-HEAD advice prompt.\ngit config --global core.pager cat\ngit config --global advice.detachedHead false\necho \"GIT_PAGER=cat\" >> \"$GITHUB_ENV\"\necho \"PAGER=cat\" >> \"$GITHUB_ENV\"\ngit checkout -B \"$DOCS_HEAD_BRANCH\"\necho \"Working branch: $(git branch --show-current)\"\n" - name: Download regenerated docs uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: @@ -458,8 +458,8 @@ jobs: path: SkiaSharpAPI/ - 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: Clone SkiaSharp (shallow, no submodules) and link the docs tree + run: "# --no-recurse-submodules on purpose: the docs format/lint pass only reads\n# in-tree files (binding/, scripts/infra/docs/, .agents/skills/api-docs/).\n# Recursing would (a) needlessly clone the huge externals/skia submodule and\n# (b) check the docs submodule out at skiasharp/docs/SkiaSharpAPI as a REAL\n# directory, which collides with the symlink below — the format glob\n# (skiasharp/docs/**/*.xml) would then walk both copies and double-count every\n# finding (~404 files reported as ~812).\ngit clone --depth 1 --branch \"$SKIASHARP_BRANCH\" --no-recurse-submodules \\\n https://github.com/mono/SkiaSharp.git skiasharp\necho \"SkiaSharp HEAD: $(git -C skiasharp rev-parse HEAD)\"\n# Point the clone's docs dir at THIS workspace's regenerated docs via a single\n# clean symlink (remove anything that might already be there first).\nrm -rf skiasharp/docs/SkiaSharpAPI\nmkdir -p skiasharp/docs\nln -sfn \"$(pwd)/SkiaSharpAPI\" skiasharp/docs/SkiaSharpAPI\n# Fail fast if the docs tree is duplicated: the linked view must contain exactly\n# the same number of XML files as the workspace (one copy, no nesting).\nws=$(find -L SkiaSharpAPI -name '*.xml' | wc -l | tr -d ' ')\nlk=$(find -L skiasharp/docs/SkiaSharpAPI -name '*.xml' | wc -l | tr -d ' ')\necho \"docs xml — workspace=$ws linked=$lk\"\ntest \"$ws\" = \"$lk\" || { echo \"::error::docs tree duplicated ($ws vs $lk)\"; exit 1; }\ncd skiasharp && dotnet tool restore\n" - name: Download container images run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.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 @@ -468,9 +468,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_6d6246d5000019fb_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_f4c681bec6c84f2d_EOF' {"create_pull_request":{"base_branch":"${{ inputs.docs_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_6d6246d5000019fb_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_f4c681bec6c84f2d_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -676,7 +676,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_cf9b0e53c449db24_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_bce300219d42379c_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -724,7 +724,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_cf9b0e53c449db24_EOF + GH_AW_MCP_CONFIG_bce300219d42379c_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 e2cb9a4..803ed91 100644 --- a/.github/workflows/auto-api-docs-writer.md +++ b/.github/workflows/auto-api-docs-writer.md @@ -155,6 +155,12 @@ pre-agent-steps: env: DOCS_HEAD_BRANCH: ${{ inputs.docs_head_branch || 'automation/write-api-docs' }} run: | + # Unattended runs must never block on an interactive git pager or a + # detached-HEAD advice prompt. + git config --global core.pager cat + git config --global advice.detachedHead false + echo "GIT_PAGER=cat" >> "$GITHUB_ENV" + echo "PAGER=cat" >> "$GITHUB_ENV" git checkout -B "$DOCS_HEAD_BRANCH" echo "Working branch: $(git branch --show-current)" @@ -164,15 +170,31 @@ pre-agent-steps: name: docs-regenerated path: SkiaSharpAPI/ - - name: Clone SkiaSharp (shallow, with submodules) + - name: Clone SkiaSharp (shallow, no submodules) and link the docs tree env: SKIASHARP_BRANCH: ${{ inputs.skiasharp_branch || 'main' }} run: | - git clone --depth 1 --branch "$SKIASHARP_BRANCH" \ - --recurse-submodules --shallow-submodules \ + # --no-recurse-submodules on purpose: the docs format/lint pass only reads + # in-tree files (binding/, scripts/infra/docs/, .agents/skills/api-docs/). + # Recursing would (a) needlessly clone the huge externals/skia submodule and + # (b) check the docs submodule out at skiasharp/docs/SkiaSharpAPI as a REAL + # directory, which collides with the symlink below — the format glob + # (skiasharp/docs/**/*.xml) would then walk both copies and double-count every + # finding (~404 files reported as ~812). + git clone --depth 1 --branch "$SKIASHARP_BRANCH" --no-recurse-submodules \ https://github.com/mono/SkiaSharp.git skiasharp + echo "SkiaSharp HEAD: $(git -C skiasharp rev-parse HEAD)" + # Point the clone's docs dir at THIS workspace's regenerated docs via a single + # clean symlink (remove anything that might already be there first). + rm -rf skiasharp/docs/SkiaSharpAPI mkdir -p skiasharp/docs ln -sfn "$(pwd)/SkiaSharpAPI" skiasharp/docs/SkiaSharpAPI + # Fail fast if the docs tree is duplicated: the linked view must contain exactly + # the same number of XML files as the workspace (one copy, no nesting). + ws=$(find -L SkiaSharpAPI -name '*.xml' | wc -l | tr -d ' ') + lk=$(find -L skiasharp/docs/SkiaSharpAPI -name '*.xml' | wc -l | tr -d ' ') + echo "docs xml — workspace=$ws linked=$lk" + test "$ws" = "$lk" || { echo "::error::docs tree duplicated ($ws vs $lk)"; exit 1; } cd skiasharp && dotnet tool restore # -- Post-agent steps (host) ------------------------------------------ From 13701a4911d2be8cf8b2c69ab0ec420d66d72a95 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Tue, 30 Jun 2026 09:35:13 +0200 Subject: [PATCH 21/21] Drop stale obsolete-in-example reference from the run prompt The deterministic obsolete-in-example linter check was removed (obsolete usage is now a reviewer judgement guided by obsolete-api-map.md). Update the run procedure so it no longer lists obsolete-in-example as a collectable finding, and point the Fix step at reviewer B instead. 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 803ed91..5be6f7e 100644 --- a/.github/workflows/auto-api-docs-writer.md +++ b/.github/workflows/auto-api-docs-writer.md @@ -223,7 +223,7 @@ list, **work** the files, then run it **again** to validate. It is **only the li correctness work is the three reviewers in `references/reviewing.md`, which it cannot do. 1. **Collect.** `cd skiasharp && dotnet cake --target=docs-format-docs && cd ..` — capture its `[docs]` - findings (obsolete-in-example, accessor-verb, spelling, repeated-word, …). Those, plus any newly-introduced + findings (accessor-verb, spelling, repeated-word, missing-docs, …). Those, plus any newly-introduced `To be added.` placeholders (uncommitted stub changes under `SkiaSharpAPI/`) and the files changed vs the base, are your work set. The run also reformats in place (idempotent, harmless). @@ -233,8 +233,9 @@ correctness work is the three reviewers in `references/reviewing.md`, which it c vs source, cite `path:line`), **B. Examples** (every snippet compiles, real APIs, **no obsolete members**), **C. Quality** (.NET conventions, completeness, style). The deterministic findings only seed this — the factual/example/quality errors are yours to find and are where the real problems hide. - - **Fix** CRITICAL findings (and every obsolete-in-example) by editing the XML directly; where a central - type is example-poor, add one correct, non-obsolete example. Touch only `` content. + - **Fix** CRITICAL findings by editing the XML directly; obsolete members in examples are caught by + reviewer B (the linter does not flag them — `obsolete-api-map.md` explains why). Where a central type + is example-poor, add one correct, non-obsolete example. Touch only `` content. Work in batches of ~25–40 files. **Timebox fixing to ~10 minutes**, then stop — a smaller PR beats none. 3. **Validate.** Re-run `cd skiasharp && dotnet cake --target=docs-format-docs && cd ..` and fix anything it