From eb153a679d2b478ba100cb5995100c5ff1dbf5d3 Mon Sep 17 00:00:00 2001 From: Gerald Versluis Date: Thu, 19 Mar 2026 16:45:45 +0100 Subject: [PATCH 1/2] Add PR documentation check workflow Adds a GitHub Actions workflow that analyzes merged PRs using Copilot CLI to determine if documentation updates are needed on dotnet/docs-maui. The workflow has two jobs: - A cheap filter job that skips bot PRs, backports, test/infra-only changes, and fast-tracks PublicAPI.Unshipped.txt changes - An expensive analysis job that uses Copilot CLI to generate specific documentation recommendations and creates tracking issues on docs-maui Inspired by dotnet/aspire's pr-docs-hook workflow, adapted for MAUI's repo structure, PR volume, and docs-maui integration. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/prompts/docs-audit-prompt.md | 33 +++ .github/workflows/pr-docs-hook.yml | 341 +++++++++++++++++++++++++++ 2 files changed, 374 insertions(+) create mode 100644 .github/prompts/docs-audit-prompt.md create mode 100644 .github/workflows/pr-docs-hook.yml diff --git a/.github/prompts/docs-audit-prompt.md b/.github/prompts/docs-audit-prompt.md new file mode 100644 index 000000000000..1b55a470537c --- /dev/null +++ b/.github/prompts/docs-audit-prompt.md @@ -0,0 +1,33 @@ +Analyze the following PR diff and generate detailed documentation requirements for a coding agent to implement. + +Review the changes for: +- New public APIs, methods, classes, or interfaces (check for PublicAPI.Unshipped.txt changes) +- New features or capabilities +- Breaking changes requiring migration guides +- New configuration options, MSBuild properties, or settings +- Behavioral changes to existing controls, layouts, or platform behavior +- New platform-specific features (Android, iOS, macCatalyst, Windows) +- New XAML markup extensions, attached properties, or bindable properties +- Changes to .NET MAUI Essentials APIs + +Before making recommendations, check the .NET MAUI documentation repository at https://github.com/dotnet/docs-maui to: +- Identify existing documentation pages that should be updated +- Determine if a new documentation page should be created +- Find related documentation that needs cross-references +- Understand the current documentation structure and patterns + +If documentation is needed, provide: +1. A clear title for the documentation task +2. Whether to UPDATE existing page(s) or CREATE new page(s) + - For updates: List the specific file paths in the docs-maui repo that need updating + - For new pages: Suggest the file path and location where the new page should be added +3. Specific sections that need to be created or updated +4. Key points that must be covered in each section +5. Code examples that should be included (with placeholders for actual code) +6. Any warnings, notes, or best practices to document +7. Cross-references to related documentation pages +8. Platform-specific documentation needs (if the change is platform-specific) + +If no documentation is needed (e.g., internal refactoring, test-only changes, CI/build changes, or bug fixes with no user-facing behavioral change), respond with exactly 'NO_DOCS_NEEDED'. + +Format your response as a structured issue description that a coding agent can follow to implement the documentation. diff --git a/.github/workflows/pr-docs-hook.yml b/.github/workflows/pr-docs-hook.yml new file mode 100644 index 000000000000..9217e43a4d72 --- /dev/null +++ b/.github/workflows/pr-docs-hook.yml @@ -0,0 +1,341 @@ +name: PR Documentation Check + +on: + pull_request_target: + types: [closed] + workflow_dispatch: + inputs: + pr_number: + description: 'PR number to analyze' + required: true + type: number + +permissions: + pull-requests: write + contents: read + issues: write + +jobs: + # Cheap filter job — skips expensive Copilot analysis for PRs that clearly don't need docs + filter: + if: (github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch') && github.repository_owner == 'dotnet' + runs-on: ubuntu-latest + outputs: + should_analyze: ${{ steps.filter.outputs.should_analyze }} + pr_number: ${{ steps.pr.outputs.number }} + pr_title: ${{ steps.filter.outputs.pr_title }} + pr_author: ${{ steps.filter.outputs.pr_author }} + steps: + - name: Set PR number + id: pr + run: | + if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then + echo "number=${{ inputs.pr_number }}" >> $GITHUB_OUTPUT + else + echo "number=${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT + fi + + - name: Check if analysis is needed + id: filter + uses: actions/github-script@v7 + with: + script: | + const prNumber = ${{ steps.pr.outputs.number }}; + + // Fetch PR metadata + const { data: pr } = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: prNumber + }); + + const author = pr.user.login; + const title = pr.title; + const labels = pr.labels.map(l => l.name); + + core.setOutput('pr_title', title); + core.setOutput('pr_author', author); + + // --- TIER 1: Hard exclusions --- + + // 1a. Bot authors (dependency updates, backports, CI automation) + const botUsers = [ + 'dotnet-maestro[bot]', + 'dotnet-maestro-bot', + 'dependabot[bot]', + 'github-actions[bot]' + ]; + if (botUsers.includes(author)) { + core.info(`Skipping: bot author '${author}'`); + core.setOutput('should_analyze', 'false'); + return; + } + + // 1b. Backport PRs (original PR already evaluated) + if (labels.some(l => l === 'backport' || l.startsWith('backport/'))) { + core.info('Skipping: backport PR'); + core.setOutput('should_analyze', 'false'); + return; + } + + // --- Fetch changed files --- + const files = await github.paginate( + github.rest.pulls.listFiles, + { owner: context.repo.owner, repo: context.repo.repo, pull_number: prNumber } + ); + const paths = files.map(f => f.filename); + + if (paths.length === 0) { + core.info('Skipping: no changed files'); + core.setOutput('should_analyze', 'false'); + return; + } + + // --- TIER 1: High-signal inclusion (PublicAPI changes) --- + const hasPublicApiChange = paths.some(p => p.includes('PublicAPI.Unshipped.txt')); + if (hasPublicApiChange) { + core.info('Including: PublicAPI.Unshipped.txt changed (high signal)'); + core.setOutput('should_analyze', 'true'); + return; + } + + // --- TIER 2: Path-based exclusions (only if ALL files match) --- + + const infraPatterns = [ + /^eng\//, + /^\.github\//, + /^build\./, + /^\.editorconfig$/, + /^\.gitignore$/, + /^NuGet\.config$/, + /^global\.json$/, + /^Directory\.Build\./, + /^tools\//, + /\.(yml|yaml)$/, + /\.(props|targets)$/ + ]; + + const testPatterns = [ + /\/tests?\//i, + /\/TestUtils\//, + /\/TestCases\./, + /\/DeviceTests\//, + /\.Tests?\./, + /\.UnitTests\./, + /\.DeviceTests\./ + ]; + + const docsPatterns = [ + /\.md$/, + /^docs\//, + /^LICENSE/, + /^THIRD-PARTY/ + ]; + + const nonDocPatterns = [...infraPatterns, ...testPatterns, ...docsPatterns]; + + const allNonDoc = paths.every(p => + nonDocPatterns.some(pattern => pattern.test(p)) + ); + + if (allNonDoc) { + core.info('Skipping: all changed files are infra/test/docs only'); + core.setOutput('should_analyze', 'false'); + return; + } + + // --- TIER 2: Template changes (always analyze) --- + const hasTemplateChange = paths.some(p => p.startsWith('src/Templates/')); + if (hasTemplateChange) { + core.info('Including: template files changed'); + core.setOutput('should_analyze', 'true'); + return; + } + + // --- TIER 2: Core source paths (analyze if touched) --- + const coreSourcePatterns = [ + /^src\/Controls\/src\//, + /^src\/Core\/src\//, + /^src\/Essentials\/src\//, + /^src\/Graphics\/src\//, + /^src\/BlazorWebView\/src\// + ]; + + const touchesCoreSource = paths.some(p => + coreSourcePatterns.some(pattern => pattern.test(p)) + ); + + if (touchesCoreSource) { + core.info('Including: core source files changed'); + core.setOutput('should_analyze', 'true'); + return; + } + + // Default: skip analysis + core.info('Skipping: no documentation-relevant changes detected'); + core.setOutput('should_analyze', 'false'); + + # Expensive analysis job — only runs when filter says so + analyze: + needs: filter + if: needs.filter.outputs.should_analyze == 'true' + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '24' + + - name: Install GitHub Copilot CLI + run: | + npm install -g @github/copilot || { + echo "Error: Failed to install GitHub Copilot CLI" + exit 1 + } + + - name: Get PR diff + id: diff + run: | + if ! gh pr diff ${{ needs.filter.outputs.pr_number }} > pr-diff.txt; then + echo "Error: Failed to fetch diff for PR #${{ needs.filter.outputs.pr_number }}" + exit 1 + fi + + if [ ! -s pr-diff.txt ]; then + echo "Warning: PR diff is empty, no changes to analyze" + echo "NO_DOCS_NEEDED" > pr-diff.txt + fi + + # Truncate very large diffs to avoid token limits + if [ $(wc -c < pr-diff.txt) -gt 100000 ]; then + echo "Warning: Diff is very large, truncating to 100KB" + head -c 100000 pr-diff.txt > pr-diff-truncated.txt + mv pr-diff-truncated.txt pr-diff.txt + echo -e "\n\n[TRUNCATED - diff exceeded 100KB]" >> pr-diff.txt + fi + env: + GH_TOKEN: ${{ github.token }} + + - name: Generate documentation requirements with Copilot + id: analyze + run: | + if [ ! -f .github/prompts/docs-audit-prompt.md ]; then + echo "Error: Prompt file .github/prompts/docs-audit-prompt.md not found" + exit 1 + fi + + cat .github/prompts/docs-audit-prompt.md > prompt.txt + echo "" >> prompt.txt + echo "PR #${{ needs.filter.outputs.pr_number }}: ${{ needs.filter.outputs.pr_title }}" >> prompt.txt + echo "" >> prompt.txt + echo "PR Diff:" >> prompt.txt + cat pr-diff.txt >> prompt.txt + + if ! copilot -p "$(cat prompt.txt)" > analysis.txt 2>copilot-error.log; then + echo "Error: Copilot CLI failed" + echo "Error details:" + cat copilot-error.log + exit 1 + fi + + if [ ! -s analysis.txt ]; then + echo "Error: Copilot produced no output" + exit 1 + fi + + cat analysis.txt + env: + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + + - name: Check if docs needed + id: docs_check + run: | + if grep -q "NO_DOCS_NEEDED" analysis.txt; then + echo "docs_needed=false" >> $GITHUB_OUTPUT + else + echo "docs_needed=true" >> $GITHUB_OUTPUT + fi + + - name: Output documentation analysis results + run: | + if [ ! -f analysis.txt ]; then + echo "Error: Analysis file not found" + exit 1 + fi + + echo "============================================" + echo "Documentation Analysis for PR #${{ needs.filter.outputs.pr_number }}" + echo "PR Title: ${{ needs.filter.outputs.pr_title }}" + echo "PR Author: ${{ needs.filter.outputs.pr_author }}" + echo "============================================" + echo "" + + if [ "${{ steps.docs_check.outputs.docs_needed }}" == "false" ]; then + echo "✅ No documentation updates required" + else + echo "📝 Documentation updates recommended:" + echo "" + cat analysis.txt + fi + + echo "" + echo "============================================" + + - name: Create issue on docs-maui repo + if: steps.docs_check.outputs.docs_needed == 'true' + id: create_issue + run: | + ISSUE_BODY="## Documentation Update Required + + This issue was automatically created based on changes from dotnet/maui PR #${{ needs.filter.outputs.pr_number }}. + + **PR Title:** ${{ needs.filter.outputs.pr_title }} + **PR Author:** @${{ needs.filter.outputs.pr_author }} + **PR Link:** https://github.com/${{ github.repository }}/pull/${{ needs.filter.outputs.pr_number }} + + ## Recommended Documentation Updates + + $(cat analysis.txt) + " + + ISSUE_URL=$(gh issue create \ + --repo dotnet/docs-maui \ + --title "Docs update for dotnet/maui#${{ needs.filter.outputs.pr_number }}: ${{ needs.filter.outputs.pr_title }}" \ + --body "$ISSUE_BODY" \ + --label "docs-from-code") + + echo "issue_url=$ISSUE_URL" >> $GITHUB_OUTPUT + env: + GH_TOKEN: ${{ github.token }} + + - name: Comment on PR + run: | + if [ "${{ steps.docs_check.outputs.docs_needed }}" == "true" ]; then + COMMENT="## 📝 Documentation Update Needed + + Hey @${{ needs.filter.outputs.pr_author }}! This PR may require documentation updates. + + An issue has been created to track the documentation work: ${{ steps.create_issue.outputs.issue_url }} + +
+ Recommended Updates + + $(cat analysis.txt) + +
" + else + COMMENT="## ✅ No Documentation Updates Needed + + Hey @${{ needs.filter.outputs.pr_author }}! This PR does not appear to require documentation updates. + + For more details, check the [workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})." + fi + + gh pr comment ${{ needs.filter.outputs.pr_number }} --body "$COMMENT" + env: + GH_TOKEN: ${{ github.token }} From 314425174cf7570542dcfe678eacdd9131e14fa0 Mon Sep 17 00:00:00 2001 From: Gerald Versluis Date: Thu, 19 Mar 2026 16:59:15 +0100 Subject: [PATCH 2/2] Replace PR docs workflow with agentic workflow Replaces the traditional GitHub Actions workflow (pr-docs-hook.yml) with a GitHub Agentic Workflow (pr-docs-check.md) for analyzing merged PRs for documentation needs on dotnet/docs-maui. Changes: - New: .github/workflows/pr-docs-check.md (agentic workflow) - Removed: .github/workflows/pr-docs-hook.yml (old shell-based workflow) - Removed: .github/prompts/docs-audit-prompt.md (prompt now inline in .md) - Updated: .gitignore (add .github/aw/logs/) The agentic workflow: - Uses natural language instructions instead of bash scripts - Authenticates cross-repo via GitHub App (no PAT expiration) - Uses safe-outputs for controlled issue creation and PR commenting - Includes smart filtering: skips bot PRs, backports, test/infra-only changes - Always analyzes PublicAPI.Unshipped.txt and template changes - Triggers on merges to main, net*.0, and release/* branches Prerequisites: - GitHub App with Issues (Read & Write) on dotnet/docs-maui - Secrets: DOCS_APP_ID (variable) and DOCS_APP_PRIVATE_KEY (secret) - Label 'docs-from-code' on dotnet/docs-maui Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/prompts/docs-audit-prompt.md | 33 --- .github/workflows/pr-docs-check.md | 162 +++++++++++++ .github/workflows/pr-docs-hook.yml | 341 --------------------------- .gitignore | 3 + 4 files changed, 165 insertions(+), 374 deletions(-) delete mode 100644 .github/prompts/docs-audit-prompt.md create mode 100644 .github/workflows/pr-docs-check.md delete mode 100644 .github/workflows/pr-docs-hook.yml diff --git a/.github/prompts/docs-audit-prompt.md b/.github/prompts/docs-audit-prompt.md deleted file mode 100644 index 1b55a470537c..000000000000 --- a/.github/prompts/docs-audit-prompt.md +++ /dev/null @@ -1,33 +0,0 @@ -Analyze the following PR diff and generate detailed documentation requirements for a coding agent to implement. - -Review the changes for: -- New public APIs, methods, classes, or interfaces (check for PublicAPI.Unshipped.txt changes) -- New features or capabilities -- Breaking changes requiring migration guides -- New configuration options, MSBuild properties, or settings -- Behavioral changes to existing controls, layouts, or platform behavior -- New platform-specific features (Android, iOS, macCatalyst, Windows) -- New XAML markup extensions, attached properties, or bindable properties -- Changes to .NET MAUI Essentials APIs - -Before making recommendations, check the .NET MAUI documentation repository at https://github.com/dotnet/docs-maui to: -- Identify existing documentation pages that should be updated -- Determine if a new documentation page should be created -- Find related documentation that needs cross-references -- Understand the current documentation structure and patterns - -If documentation is needed, provide: -1. A clear title for the documentation task -2. Whether to UPDATE existing page(s) or CREATE new page(s) - - For updates: List the specific file paths in the docs-maui repo that need updating - - For new pages: Suggest the file path and location where the new page should be added -3. Specific sections that need to be created or updated -4. Key points that must be covered in each section -5. Code examples that should be included (with placeholders for actual code) -6. Any warnings, notes, or best practices to document -7. Cross-references to related documentation pages -8. Platform-specific documentation needs (if the change is platform-specific) - -If no documentation is needed (e.g., internal refactoring, test-only changes, CI/build changes, or bug fixes with no user-facing behavioral change), respond with exactly 'NO_DOCS_NEEDED'. - -Format your response as a structured issue description that a coding agent can follow to implement the documentation. diff --git a/.github/workflows/pr-docs-check.md b/.github/workflows/pr-docs-check.md new file mode 100644 index 000000000000..2463ac3311b2 --- /dev/null +++ b/.github/workflows/pr-docs-check.md @@ -0,0 +1,162 @@ +--- +description: | + Analyzes merged pull requests for documentation needs. When a PR is merged, + this workflow reviews the changes and determines if documentation updates are + required on dotnet/docs-maui. If so, it creates a tracking issue and comments + on the PR with a link. + +on: + pull_request: + types: [closed] + branches: + - main + - net*.0 + - release/* + workflow_dispatch: + inputs: + pr_number: + description: "PR number to analyze" + required: true + type: string + +if: >- + (github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch') + && github.repository_owner == 'dotnet' + +permissions: + contents: read + pull-requests: read + issues: read + +network: + allowed: + - defaults + - github + +tools: + github: + toolsets: [repos, issues, pull_requests] + app: + app-id: ${{ vars.DOCS_APP_ID }} + private-key: ${{ secrets.DOCS_APP_PRIVATE_KEY }} + owner: "dotnet" + repositories: ["docs-maui"] + web-fetch: + +safe-outputs: + create-issue: + title-prefix: "[docs] " + labels: [docs-from-code] + target-repo: "dotnet/docs-maui" + expires: false + add-comment: + hide-older-comments: true + +timeout-minutes: 15 +--- + +# PR Documentation Check + +Analyze a merged pull request in `dotnet/maui` and determine whether documentation +updates are needed on the `dotnet/docs-maui` documentation site. + +## Context + +- **Repository**: `${{ github.repository }}` +- **PR Number**: `${{ github.event.pull_request.number || github.event.inputs.pr_number }}` +- **PR Title**: `${{ github.event.pull_request.title }}` + +## Step 1: Gather PR Information + +Use the GitHub tools to read the full pull request details for the PR number above, +including the title, description, author, base branch, and the full diff of changes. +Pay special attention to the **base branch** and the **PR author** username, as both +are needed in later steps. + +If this was triggered via `workflow_dispatch`, use the `pr_number` input to look up +the PR details. + +## Step 2: Quick Filter — Skip Obviously Non-Doc PRs + +Before doing a deep analysis, check if this PR can be **skipped entirely**: + +**Skip if ALL of these are true:** +- PR author is a bot (`dotnet-maestro[bot]`, `dependabot[bot]`, `github-actions[bot]`) +- OR PR has a `backport` label (or any label starting with `backport/`) + +**Skip if ALL changed files** fall into these categories (no other files changed): +- Build infrastructure: `eng/`, `.github/`, `build.*`, `*.yml`, `*.yaml`, + `*.props`, `*.targets`, `Directory.Build.*`, `NuGet.config`, `global.json`, `tools/` +- Tests only: paths containing `/tests/`, `/TestUtils/`, `/TestCases.`, + `/DeviceTests/`, or files with `.Tests.`, `.UnitTests.`, `.DeviceTests.` in name +- Documentation/meta: `*.md`, `docs/`, `LICENSE*`, `THIRD-PARTY*` + +If the PR is skipped, comment on the PR with a brief message confirming no +documentation updates are required and the reason (e.g., "bot PR", "test-only +changes"), then stop. + +**Always proceed with analysis if:** +- Any `PublicAPI.Unshipped.txt` file was changed (strong signal of API changes) +- Any file under `src/Templates/` was changed (affects getting-started docs) + +## Step 3: Analyze Changes for Documentation Needs + +Review the PR diff and metadata for the following types of user-facing changes that +warrant documentation: + +- **New public APIs**: new methods, classes, interfaces, bindable properties, + attached properties, or XAML markup extensions +- **New features or capabilities**: new controls, layouts, platform behaviors, + or Essentials APIs +- **Breaking changes**: removed or renamed APIs, behavioral changes, migration needs +- **New configuration options**: MSBuild properties, platform settings, or parameters +- **Platform-specific features**: new Android, iOS, macCatalyst, or Windows behaviors +- **Template changes**: changes to project templates affecting new project creation +- **Significant behavioral changes**: changes to navigation, layout, rendering, + gestures, or lifecycle + +Changes that do NOT typically need documentation: +- Internal refactoring with no public API surface changes +- Test-only changes +- Build/CI infrastructure changes +- Bug fixes that don't change documented behavior +- Dependency version bumps +- Code style or formatting changes + +## Step 4: Check Existing Documentation + +If you determine documentation may be needed, use the GitHub tools to browse the +`dotnet/docs-maui` repository to: + +- Identify existing documentation pages that cover the affected feature area +- Determine whether existing pages need updates or new pages should be created +- Understand the current documentation structure and naming conventions + +## Step 5: Take Action + +### If documentation IS needed: + +1. **Create an issue** on `dotnet/docs-maui` with: + - A clear title describing the documentation work needed + - A structured body that includes: + - Link to the source PR in `dotnet/maui` + - PR author mention + - Whether to UPDATE existing pages or CREATE new pages + - Specific file paths in the `docs-maui` repo that need updating (if applicable) + - Detailed description of what sections need to be created or updated + - Key points that must be covered + - Suggested code examples (with placeholders if needed) + - Platform-specific documentation needs (if applicable) + - Cross-references to related documentation pages + +2. **Comment on the PR** in `dotnet/maui` with: + - A summary indicating documentation updates are needed + - A link to the created docs issue + - A brief description of what documentation changes are recommended + +### If documentation is NOT needed: + +1. **Comment on the PR** in `dotnet/maui` with: + - A brief message confirming no documentation updates are required + - A short explanation of why (e.g., "internal refactoring only", + "bug fix with no behavioral change") diff --git a/.github/workflows/pr-docs-hook.yml b/.github/workflows/pr-docs-hook.yml deleted file mode 100644 index 9217e43a4d72..000000000000 --- a/.github/workflows/pr-docs-hook.yml +++ /dev/null @@ -1,341 +0,0 @@ -name: PR Documentation Check - -on: - pull_request_target: - types: [closed] - workflow_dispatch: - inputs: - pr_number: - description: 'PR number to analyze' - required: true - type: number - -permissions: - pull-requests: write - contents: read - issues: write - -jobs: - # Cheap filter job — skips expensive Copilot analysis for PRs that clearly don't need docs - filter: - if: (github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch') && github.repository_owner == 'dotnet' - runs-on: ubuntu-latest - outputs: - should_analyze: ${{ steps.filter.outputs.should_analyze }} - pr_number: ${{ steps.pr.outputs.number }} - pr_title: ${{ steps.filter.outputs.pr_title }} - pr_author: ${{ steps.filter.outputs.pr_author }} - steps: - - name: Set PR number - id: pr - run: | - if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then - echo "number=${{ inputs.pr_number }}" >> $GITHUB_OUTPUT - else - echo "number=${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT - fi - - - name: Check if analysis is needed - id: filter - uses: actions/github-script@v7 - with: - script: | - const prNumber = ${{ steps.pr.outputs.number }}; - - // Fetch PR metadata - const { data: pr } = await github.rest.pulls.get({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: prNumber - }); - - const author = pr.user.login; - const title = pr.title; - const labels = pr.labels.map(l => l.name); - - core.setOutput('pr_title', title); - core.setOutput('pr_author', author); - - // --- TIER 1: Hard exclusions --- - - // 1a. Bot authors (dependency updates, backports, CI automation) - const botUsers = [ - 'dotnet-maestro[bot]', - 'dotnet-maestro-bot', - 'dependabot[bot]', - 'github-actions[bot]' - ]; - if (botUsers.includes(author)) { - core.info(`Skipping: bot author '${author}'`); - core.setOutput('should_analyze', 'false'); - return; - } - - // 1b. Backport PRs (original PR already evaluated) - if (labels.some(l => l === 'backport' || l.startsWith('backport/'))) { - core.info('Skipping: backport PR'); - core.setOutput('should_analyze', 'false'); - return; - } - - // --- Fetch changed files --- - const files = await github.paginate( - github.rest.pulls.listFiles, - { owner: context.repo.owner, repo: context.repo.repo, pull_number: prNumber } - ); - const paths = files.map(f => f.filename); - - if (paths.length === 0) { - core.info('Skipping: no changed files'); - core.setOutput('should_analyze', 'false'); - return; - } - - // --- TIER 1: High-signal inclusion (PublicAPI changes) --- - const hasPublicApiChange = paths.some(p => p.includes('PublicAPI.Unshipped.txt')); - if (hasPublicApiChange) { - core.info('Including: PublicAPI.Unshipped.txt changed (high signal)'); - core.setOutput('should_analyze', 'true'); - return; - } - - // --- TIER 2: Path-based exclusions (only if ALL files match) --- - - const infraPatterns = [ - /^eng\//, - /^\.github\//, - /^build\./, - /^\.editorconfig$/, - /^\.gitignore$/, - /^NuGet\.config$/, - /^global\.json$/, - /^Directory\.Build\./, - /^tools\//, - /\.(yml|yaml)$/, - /\.(props|targets)$/ - ]; - - const testPatterns = [ - /\/tests?\//i, - /\/TestUtils\//, - /\/TestCases\./, - /\/DeviceTests\//, - /\.Tests?\./, - /\.UnitTests\./, - /\.DeviceTests\./ - ]; - - const docsPatterns = [ - /\.md$/, - /^docs\//, - /^LICENSE/, - /^THIRD-PARTY/ - ]; - - const nonDocPatterns = [...infraPatterns, ...testPatterns, ...docsPatterns]; - - const allNonDoc = paths.every(p => - nonDocPatterns.some(pattern => pattern.test(p)) - ); - - if (allNonDoc) { - core.info('Skipping: all changed files are infra/test/docs only'); - core.setOutput('should_analyze', 'false'); - return; - } - - // --- TIER 2: Template changes (always analyze) --- - const hasTemplateChange = paths.some(p => p.startsWith('src/Templates/')); - if (hasTemplateChange) { - core.info('Including: template files changed'); - core.setOutput('should_analyze', 'true'); - return; - } - - // --- TIER 2: Core source paths (analyze if touched) --- - const coreSourcePatterns = [ - /^src\/Controls\/src\//, - /^src\/Core\/src\//, - /^src\/Essentials\/src\//, - /^src\/Graphics\/src\//, - /^src\/BlazorWebView\/src\// - ]; - - const touchesCoreSource = paths.some(p => - coreSourcePatterns.some(pattern => pattern.test(p)) - ); - - if (touchesCoreSource) { - core.info('Including: core source files changed'); - core.setOutput('should_analyze', 'true'); - return; - } - - // Default: skip analysis - core.info('Skipping: no documentation-relevant changes detected'); - core.setOutput('should_analyze', 'false'); - - # Expensive analysis job — only runs when filter says so - analyze: - needs: filter - if: needs.filter.outputs.should_analyze == 'true' - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '24' - - - name: Install GitHub Copilot CLI - run: | - npm install -g @github/copilot || { - echo "Error: Failed to install GitHub Copilot CLI" - exit 1 - } - - - name: Get PR diff - id: diff - run: | - if ! gh pr diff ${{ needs.filter.outputs.pr_number }} > pr-diff.txt; then - echo "Error: Failed to fetch diff for PR #${{ needs.filter.outputs.pr_number }}" - exit 1 - fi - - if [ ! -s pr-diff.txt ]; then - echo "Warning: PR diff is empty, no changes to analyze" - echo "NO_DOCS_NEEDED" > pr-diff.txt - fi - - # Truncate very large diffs to avoid token limits - if [ $(wc -c < pr-diff.txt) -gt 100000 ]; then - echo "Warning: Diff is very large, truncating to 100KB" - head -c 100000 pr-diff.txt > pr-diff-truncated.txt - mv pr-diff-truncated.txt pr-diff.txt - echo -e "\n\n[TRUNCATED - diff exceeded 100KB]" >> pr-diff.txt - fi - env: - GH_TOKEN: ${{ github.token }} - - - name: Generate documentation requirements with Copilot - id: analyze - run: | - if [ ! -f .github/prompts/docs-audit-prompt.md ]; then - echo "Error: Prompt file .github/prompts/docs-audit-prompt.md not found" - exit 1 - fi - - cat .github/prompts/docs-audit-prompt.md > prompt.txt - echo "" >> prompt.txt - echo "PR #${{ needs.filter.outputs.pr_number }}: ${{ needs.filter.outputs.pr_title }}" >> prompt.txt - echo "" >> prompt.txt - echo "PR Diff:" >> prompt.txt - cat pr-diff.txt >> prompt.txt - - if ! copilot -p "$(cat prompt.txt)" > analysis.txt 2>copilot-error.log; then - echo "Error: Copilot CLI failed" - echo "Error details:" - cat copilot-error.log - exit 1 - fi - - if [ ! -s analysis.txt ]; then - echo "Error: Copilot produced no output" - exit 1 - fi - - cat analysis.txt - env: - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - - - name: Check if docs needed - id: docs_check - run: | - if grep -q "NO_DOCS_NEEDED" analysis.txt; then - echo "docs_needed=false" >> $GITHUB_OUTPUT - else - echo "docs_needed=true" >> $GITHUB_OUTPUT - fi - - - name: Output documentation analysis results - run: | - if [ ! -f analysis.txt ]; then - echo "Error: Analysis file not found" - exit 1 - fi - - echo "============================================" - echo "Documentation Analysis for PR #${{ needs.filter.outputs.pr_number }}" - echo "PR Title: ${{ needs.filter.outputs.pr_title }}" - echo "PR Author: ${{ needs.filter.outputs.pr_author }}" - echo "============================================" - echo "" - - if [ "${{ steps.docs_check.outputs.docs_needed }}" == "false" ]; then - echo "✅ No documentation updates required" - else - echo "📝 Documentation updates recommended:" - echo "" - cat analysis.txt - fi - - echo "" - echo "============================================" - - - name: Create issue on docs-maui repo - if: steps.docs_check.outputs.docs_needed == 'true' - id: create_issue - run: | - ISSUE_BODY="## Documentation Update Required - - This issue was automatically created based on changes from dotnet/maui PR #${{ needs.filter.outputs.pr_number }}. - - **PR Title:** ${{ needs.filter.outputs.pr_title }} - **PR Author:** @${{ needs.filter.outputs.pr_author }} - **PR Link:** https://github.com/${{ github.repository }}/pull/${{ needs.filter.outputs.pr_number }} - - ## Recommended Documentation Updates - - $(cat analysis.txt) - " - - ISSUE_URL=$(gh issue create \ - --repo dotnet/docs-maui \ - --title "Docs update for dotnet/maui#${{ needs.filter.outputs.pr_number }}: ${{ needs.filter.outputs.pr_title }}" \ - --body "$ISSUE_BODY" \ - --label "docs-from-code") - - echo "issue_url=$ISSUE_URL" >> $GITHUB_OUTPUT - env: - GH_TOKEN: ${{ github.token }} - - - name: Comment on PR - run: | - if [ "${{ steps.docs_check.outputs.docs_needed }}" == "true" ]; then - COMMENT="## 📝 Documentation Update Needed - - Hey @${{ needs.filter.outputs.pr_author }}! This PR may require documentation updates. - - An issue has been created to track the documentation work: ${{ steps.create_issue.outputs.issue_url }} - -
- Recommended Updates - - $(cat analysis.txt) - -
" - else - COMMENT="## ✅ No Documentation Updates Needed - - Hey @${{ needs.filter.outputs.pr_author }}! This PR does not appear to require documentation updates. - - For more details, check the [workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})." - fi - - gh pr comment ${{ needs.filter.outputs.pr_number }} --body "$COMMENT" - env: - GH_TOKEN: ${{ github.token }} diff --git a/.gitignore b/.gitignore index daafde085962..c8414dcfb2fd 100644 --- a/.gitignore +++ b/.gitignore @@ -391,3 +391,6 @@ temp # Gradle build reports src/Core/AndroidNative/build/reports/ + +# Agentic workflow debug logs +.github/aw/logs/