diff --git a/.github/workflows/claude-doc-sync.yml b/.github/workflows/claude-doc-sync.yml new file mode 100644 index 000000000..99100f1a0 --- /dev/null +++ b/.github/workflows/claude-doc-sync.yml @@ -0,0 +1,70 @@ +name: Sync docs on merged PRs + +# Runs after a PR merges into master and asks Claude to check whether any +# documentation (root README, package READMEs, /docs) needs updating to +# reflect the change, opening a follow-up PR only if it finds something to +# update. +# +# Gated to keep run volume (and cost) down: only master-targeted merges +# (branches: [master] below) that also carry an approving review +# (reviewDecision == APPROVED, checked in the gate step) trigger a Claude run. +# master requires 1 approval by branch protection, but enforce_admins is off, +# so an admin can still merge without one - this gate closes that gap rather +# than assuming protection alone guarantees a review happened. +# +# Uses pull_request_target (not pull_request) so the workflow runs with this +# repo's own permissions even when the merged PR came from a fork - this repo +# takes plenty of external contributions and a plain `pull_request` trigger +# gets a read-only token for fork-authored PRs, which would make `gh pr +# create` fail below. This is safe here because we only ever check out +# base.ref (the already-reviewed, already-merged default branch), never the +# PR's own head ref - we never build or execute the contributor's code. +on: + pull_request_target: + types: [closed] + branches: [master] + +permissions: + contents: write + pull-requests: write + +jobs: + doc-sync: + if: github.event.pull_request.merged == true + runs-on: ubuntu-latest + steps: + - name: Require an approved review + id: gate + env: + GH_TOKEN: ${{ github.token }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: | + decision=$(gh pr view "$PR_NUMBER" --repo "${{ github.repository }}" --json reviewDecision -q .reviewDecision) + echo "review decision for #$PR_NUMBER: ${decision:-}" + if [ "$decision" = "APPROVED" ]; then + echo "approved=true" >> "$GITHUB_OUTPUT" + else + echo "approved=false" >> "$GITHUB_OUTPUT" + fi + + - name: Checkout base branch + if: steps.gate.outputs.approved == 'true' + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + ref: ${{ github.event.pull_request.base.ref }} + fetch-depth: 0 + + - name: Sync docs with Claude + if: steps.gate.outputs.approved == 'true' + uses: anthropics/claude-code-action@16b3b310c3d7b5279df73130324d5205aeea8eac # v1 + with: + prompt: | + Pull request #${{ github.event.pull_request.number }} ("${{ github.event.pull_request.title }}") was just merged into ${{ github.event.pull_request.base.ref }}. + + 1. Run `gh pr diff ${{ github.event.pull_request.number }}` to see exactly what changed. + 2. Search this repository's documentation (root README.md, each package's README.md, and any /docs content) for anything describing the behavior, API, configuration, or usage that this PR changed. + 3. If you find documentation that's now inaccurate, incomplete, or missing details because of this change, update it to match. Do not treat CHANGELOG.md files as documentation - they're generated by release tooling, leave them alone. + 4. If no documentation needs updating, stop here: do not create a branch, commit, or pull request. + 5. If you did update documentation, create a new branch off ${{ github.event.pull_request.base.ref }} named `docs/sync-pr-${{ github.event.pull_request.number }}`, commit the changes, push it, and open a pull request against ${{ github.event.pull_request.base.ref }} with `gh pr create`. Title it "docs: sync with #${{ github.event.pull_request.number }}" and explain in the body which doc(s) you updated, why, and link back to #${{ github.event.pull_request.number }}. + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + claude_args: "--allowedTools 'Edit,MultiEdit,Write,Read,Glob,Grep,Bash(git:*),Bash(gh:*)'"