diff --git a/.github/workflows/auto-api-docs-writer.lock.yml b/.github/workflows/auto-api-docs-writer.lock.yml
index da79936..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":"d74377dfeb3220592de0b044555f57b6f2758da8ebffb69185355672984d84df","compiler_version":"v0.71.5","strict":true,"agent_id":"copilot"}
+# 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"}]}
# ___ _ _
# / _ \ | | (_)
@@ -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 (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
@@ -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
@@ -116,7 +126,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-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"
@@ -201,23 +211,23 @@ jobs:
run: |
bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh"
{
- cat << 'GH_AW_PROMPT_a9cbb40a4aa95c57_EOF'
+ cat << 'GH_AW_PROMPT_5f7c8f1dc8448757_EOF'
- GH_AW_PROMPT_a9cbb40a4aa95c57_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_a9cbb40a4aa95c57_EOF'
+ cat << 'GH_AW_PROMPT_5f7c8f1dc8448757_EOF'
Tools: create_pull_request, missing_tool, missing_data, noop
- GH_AW_PROMPT_a9cbb40a4aa95c57_EOF
+ GH_AW_PROMPT_5f7c8f1dc8448757_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md"
- cat << 'GH_AW_PROMPT_a9cbb40a4aa95c57_EOF'
+ cat << 'GH_AW_PROMPT_5f7c8f1dc8448757_EOF'
- GH_AW_PROMPT_a9cbb40a4aa95c57_EOF
+ GH_AW_PROMPT_5f7c8f1dc8448757_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md"
- cat << 'GH_AW_PROMPT_a9cbb40a4aa95c57_EOF'
+ cat << 'GH_AW_PROMPT_5f7c8f1dc8448757_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_a9cbb40a4aa95c57_EOF
+ GH_AW_PROMPT_5f7c8f1dc8448757_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md"
- cat << 'GH_AW_PROMPT_a9cbb40a4aa95c57_EOF'
+ cat << 'GH_AW_PROMPT_5f7c8f1dc8448757_EOF'
{{#runtime-import .github/workflows/auto-api-docs-writer.md}}
- GH_AW_PROMPT_a9cbb40a4aa95c57_EOF
+ GH_AW_PROMPT_5f7c8f1dc8448757_EOF
} > "$GH_AW_PROMPT"
- name: Interpolate variables and render templates
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
@@ -378,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"
@@ -436,24 +447,19 @@ 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"
+ - env:
+ DOCS_HEAD_BRANCH: ${{ inputs.docs_head_branch || 'automation/write-api-docs' }}
+ name: Use a dedicated PR branch (never the dispatch ref)
+ 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:
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
- run: |-
- mkdir -p /tmp/gh-aw/agent/docs-work-original
- cp -r output/docs-work/* /tmp/gh-aw/agent/docs-work-original/
+ 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
@@ -462,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_0f9919760fb1f6c1_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
+ 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_f4c681bec6c84f2d_EOF
- name: Generate Safe Outputs Tools
env:
GH_AW_TOOLS_META_JSON: |
@@ -670,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_dc4a8f8b337ab7b3_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": {
@@ -718,7 +724,7 @@ jobs:
"payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}"
}
}
- GH_AW_MCP_CONFIG_dc4a8f8b337ab7b3_EOF
+ GH_AW_MCP_CONFIG_bce300219d42379c_EOF
- name: Mount MCP servers as CLIs
id: mount-mcp-clis
continue-on-error: true
@@ -758,7 +764,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-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
@@ -911,10 +917,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 +1184,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 (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: |
@@ -1226,7 +1228,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-opus-4.7
GH_AW_PHASE: detection
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
GH_AW_VERSION: v0.71.5
@@ -1331,12 +1333,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 FETCH_HEAD
cd ..
+ env:
+ DOCS_BASE_BRANCH: ${{ inputs.docs_base_branch || 'main' }}
shell: bash
- name: Setup .NET
uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4
@@ -1362,23 +1366,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:
@@ -1398,7 +1391,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-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"
@@ -1447,7 +1440,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
@@ -1482,7 +1475,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 3b089a8..5be6f7e 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 (1) fills 'To be added.' placeholders [add] and (2) reviews & improves a scope of existing docs [review], editing the mdoc XML directly."
# -- Triggers ----------------------------------------------------------
on:
@@ -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 FETCH_HEAD
cd ..
- name: Setup .NET
uses: actions/setup-dotnet@v4
@@ -71,33 +83,37 @@ 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.
+# 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
cancel-in-progress: true
+# -- Engine (pin the run model) ---------------------------------------
+# 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-opus-4.7
+
# -- Agent tools -------------------------------------------------------
tools:
github:
@@ -122,118 +138,136 @@ 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
# -- Pre-agent steps (host) -------------------------------------------
pre-agent-steps:
+ # 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' }}
+ 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)"
+
- name: Download regenerated docs
uses: actions/download-artifact@v4
with:
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)
+ - 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
- - name: Save original JSON to agent artifact cache
- run: |
- mkdir -p /tmp/gh-aw/agent/docs-work-original
- cp -r output/docs-work/* /tmp/gh-aw/agent/docs-work-original/
-
# -- 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.
+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.
-## Execution order
+**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.
-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.
+## This run: format → work → format
-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
+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.
-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.
+1. **Collect.** `cd skiasharp && dotnet cake --target=docs-format-docs && cd ..` — capture its `[docs]`
+ 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).
-4. **Phase 6 (Merge)** — this is the critical step. Run:
- ```bash
- cd skiasharp && pwsh .agents/skills/api-docs/scripts/docs-tool.ps1 merge ../output/docs-work/ && cd ..
- ```
- Do NOT run `docs-format-docs` — it runs automatically as a post-step after the agent finishes.
+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 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.
-5. **Commit and PR** — commit the XML changes and create a pull request:
- ```bash
- git checkout -b automation/write-api-docs
- git add SkiaSharpAPI/
- git commit -m "Fill API documentation placeholders"
- ```
- 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.`
+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.)
-If there are no documentation changes after merging, call the `noop` tool instead.
+## Paths in this workflow
-## Critical rules
+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:
-- **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.
-- **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.
- - **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.
+| SKILL.md reference | Here |
+|---|---|
+| `docs/SkiaSharpAPI/` | `SkiaSharpAPI/` (workspace root) |
+| `.agents/skills/api-docs/` | `skiasharp/.agents/skills/api-docs/` |
+| `binding/SkiaSharp/`, `binding/HarfBuzzSharp/` | `skiasharp/binding/...` |
-## Path differences from SKILL.md
+## Commit and open the PR
-Because this workflow runs from the docs repo (not SkiaSharp), paths differ:
+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"
+ ```
+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.
-| 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`.