Files
mattermost/.github/workflows/docs-gap.yml
T
Eva SarafianouandCursor 27c501d217 Replace docs impact review with self-clearing gap detection (#38177)
* Replace docs impact review with self-clearing gap detection

The old workflow sparse-checked-out mattermost/docs and cited source/**.rst
paths that no longer exist, so it was judging the current PR against a docs
tree this repo replaced. It also applied Docs/Needed and never removed it,
which made the label a permanent record of a gap rather than a statement about
the PR as it stands.

docs-gap.yml reads both sides of the diff — the code and whatever docs the PR
already carries — so a later push that documents the change flips the verdict
to none and clears the label. The label follows the verdict, not the presence
of a docs diff, or clearing it would be an escape hatch anyone could take by
editing an unrelated page. Prior state travels in the sticky comment, so a
label a human applied survives a none verdict.

The audience personas come from the same frontmatter the review router reads
instead of being restated in workflow YAML, which is what keeps the two halves
of the pipeline from disagreeing about who reads what.

Both are added and removed in one commit: they share a trigger, so running
them together would double-comment and race the label.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Tune docs-gap prompt, CAP limits, and Action pins

Correct API docs publish scope, size wrap caps for ~10k/1k LoC and ~100 paths, and bump checkout/setup-node to v7.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Split docs-ai into review/, gap/, and shared lib/

Make which pipeline owns which scripts obvious; keep one package and shared helpers.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Trim docs-gap comments to non-obvious rationale

Keep the why for label lifecycle, soft-fail, and secrets gating; drop restated narration.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix gap-report footer, label-remove restore, and cancelled report job

Address CodeRabbit: hide opt-out on none, restore sticky if removeLabel fails, shell-safe dry-run example, skip report on concurrency cancel.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Harden sticky author match and pin claude-code-action v1.0.235

Match stickies to github-actions[bot] only, use the job GITHUB_TOKEN
instead of Claude App OIDC on analyze, and bump the action pin.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-28 12:49:16 +03:00

147 lines
5.3 KiB
YAML

name: Docs Gap Analysis
# Owns Docs/Needed. No paths: filter — must run on docs-only pushes that close a gap.
# analyze (model, read-only) and report (label/comment writer) are separate jobs.
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
branches:
- master
permissions:
contents: read
concurrency:
group: docs-gap-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
analyze:
name: Analyze documentation impact
# Skip forks (no key/token) and human opt-outs.
if: >-
github.event.pull_request.draft == false &&
github.event.pull_request.head.repo.full_name == github.repository &&
!startsWith(github.event.pull_request.user.login, 'unified-ci-app') &&
!contains(github.event.pull_request.labels.*.name, 'Docs/Not Needed') &&
!contains(github.event.pull_request.labels.*.name, 'Docs/Done')
runs-on: ubuntu-24.04
permissions:
contents: read
pull-requests: read
outputs:
analyzable: ${{ steps.diff.outputs.analyzable }}
result: ${{ steps.gap.outputs.structured_output }}
outcome: ${{ steps.gap.outcome }}
env:
HAS_ANTHROPIC_KEY: ${{ secrets.ANTHROPIC_API_KEY != '' }}
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 2
persist-credentials: false
# Job-level if cannot read secrets; fold the key check into analyzable so
# report skips when unset.
- name: Collect both sides of the diff
id: diff
run: |
set -euo pipefail
mkdir -p .docs-ai/gap
DOCS_PATHSPEC=(
docs/main docs/develop
':(exclude)docs/main/agents/docs'
)
git diff --name-only HEAD^1 HEAD > .docs-ai/gap/files.raw
git diff HEAD^1 HEAD -- . ':(exclude)docs' > .docs-ai/gap/code.raw
git diff HEAD^1 HEAD -- "${DOCS_PATHSPEC[@]}" > .docs-ai/gap/docs.raw
COUNT=$(wc -l < .docs-ai/gap/files.raw | tr -d ' ')
echo "Changed files: ${COUNT}"
if [ "${COUNT}" -eq 0 ]; then
echo "Nothing changed; skipping gap analysis." >> "$GITHUB_STEP_SUMMARY"
echo "analyzable=false" >> "$GITHUB_OUTPUT"
elif [ "$HAS_ANTHROPIC_KEY" != "true" ]; then
echo "ANTHROPIC_API_KEY is not configured; skipping gap analysis." >> "$GITHUB_STEP_SUMMARY"
echo "analyzable=false" >> "$GITHUB_OUTPUT"
else
echo "analyzable=true" >> "$GITHUB_OUTPUT"
fi
- name: Set up Node
if: steps.diff.outputs.analyzable == 'true'
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: .nvmrc
cache: "npm"
cache-dependency-path: .github/scripts/docs-ai/package-lock.json
- name: Install dependencies
if: steps.diff.outputs.analyzable == 'true'
working-directory: .github/scripts/docs-ai
run: npm ci --ignore-scripts
- name: Build the prompt
id: prompt
if: steps.diff.outputs.analyzable == 'true'
run: |
node .github/scripts/docs-ai/gap/prepare.mjs \
--files .docs-ai/gap/files.raw \
--code-diff .docs-ai/gap/code.raw \
--docs-diff .docs-ai/gap/docs.raw \
--out-dir .docs-ai/gap
# Soft-fail so report can leave the label alone on outage.
- name: Assess the gap
id: gap
if: steps.diff.outputs.analyzable == 'true'
continue-on-error: true
uses: anthropics/claude-code-action@756cc22e19660d20e8cc9496b4f242475a7f7790 # v1.0.235
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
# Without this the action uses Claude App via OIDC (needs id-token: write).
github_token: ${{ secrets.GITHUB_TOKEN }}
allowed_bots: "cursor,claude"
prompt: ${{ steps.prompt.outputs.prompt }}
claude_args: |
--model ${{ vars.DOCS_AI_GAP_MODEL || 'claude-sonnet-4-6' }}
--max-turns 30
--allowedTools "Read,Glob,Grep"
--json-schema '${{ steps.prompt.outputs.schema }}'
report:
name: Post comment and manage label
needs: analyze
# !cancelled (not always): skip superseded concurrency cancels; still run after soft-fail.
if: ${{ !cancelled() && needs.analyze.outputs.analyzable == 'true' }}
runs-on: ubuntu-24.04
permissions:
contents: read
pull-requests: write
issues: write
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Set up Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: .nvmrc
- name: Report
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
GAP_RESULT: ${{ needs.analyze.outputs.result }}
GAP_OUTCOME: ${{ needs.analyze.outputs.outcome }}
run: node .github/scripts/docs-ai/gap/report.mjs