Catalog drift #2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Catalog drift | |
| # Re-run the catalog sync scripts on a schedule and open (or update) a single | |
| # PR when upstream docs have drifted. Manual `workflow_dispatch` lets a human | |
| # kick a fresh check without waiting for the cron. | |
| # | |
| # The sync scripts always rewrite `fetchedAt` even when content is unchanged | |
| # — by design, so the catalog records when it was last looked at — so we | |
| # normalise that field out before deciding "is this real drift?". If the | |
| # only change is the timestamp, the working tree is restored to HEAD and no | |
| # PR is opened. | |
| on: | |
| schedule: | |
| # Mondays 09:00 UTC. Weekly is enough for documentation drift; the | |
| # window also gives upstream a few business days to settle after any | |
| # weekend churn before we PR ourselves. | |
| - cron: "0 9 * * 1" | |
| workflow_dispatch: | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| # Don't let two drift runs race; the second would try to recreate the same | |
| # branch and stomp the first's PR. | |
| concurrency: | |
| group: catalog-drift | |
| cancel-in-progress: false | |
| jobs: | |
| drift: | |
| name: Sync and PR on drift | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Setup Node | |
| uses: actions/setup-node@v5 | |
| with: | |
| node-version: "22" | |
| cache: "npm" | |
| - name: Install npm deps | |
| run: npm ci | |
| - name: Run sync scripts | |
| run: | | |
| npm run sync:settings | |
| npm run sync:env-vars | |
| npm run sync:hooks | |
| npm run sync:sub-agents | |
| npm run sync:mcp | |
| npm run sync:permissions | |
| npm run sync:keybindings | |
| npm run sync:cli-reference | |
| # `fetchedAt` is rewritten on every sync run — strip it before | |
| # comparing so we don't open noise PRs that only bump a timestamp. | |
| # If real content changed, leave the working tree as-is so | |
| # create-pull-request picks up the diff (timestamp included, since the | |
| # PR is meaningful). If only the timestamp changed, restore HEAD so | |
| # the working tree is clean and the PR step skips. | |
| - name: Detect real drift | |
| id: detect | |
| run: | | |
| set -euo pipefail | |
| real_drift=false | |
| drifted_files=() | |
| for f in settings env-vars hooks sub-agents mcp permissions keybindings cli-reference; do | |
| head_norm=$(git show HEAD:catalog/$f.json | jq 'del(.fetchedAt)') | |
| new_norm=$(jq 'del(.fetchedAt)' catalog/$f.json) | |
| if [ "$head_norm" != "$new_norm" ]; then | |
| real_drift=true | |
| drifted_files+=("catalog/$f.json") | |
| fi | |
| done | |
| if [ "$real_drift" = false ]; then | |
| echo "Only fetchedAt changed; restoring catalog/ from HEAD." | |
| git checkout HEAD -- catalog/ | |
| else | |
| echo "Real drift in: ${drifted_files[*]}" | |
| fi | |
| echo "real_drift=$real_drift" >> "$GITHUB_OUTPUT" | |
| # Newline-joined list for use in the PR body. printf %s\\n preserves | |
| # the array layout when there are multiple files. | |
| { | |
| echo "drifted_files<<EOF" | |
| printf "%s\n" "${drifted_files[@]}" | |
| echo "EOF" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Open or update drift PR | |
| if: steps.detect.outputs.real_drift == 'true' | |
| uses: peter-evans/create-pull-request@v8 | |
| with: | |
| branch: chore/catalog-drift | |
| # Update the same branch on subsequent runs so the PR moves with | |
| # upstream rather than spawning a new one each Monday. | |
| base: main | |
| commit-message: | | |
| chore: sync catalog with upstream docs | |
| Auto-generated by .github/workflows/catalog-drift.yml. | |
| title: "chore: sync catalog with upstream docs" | |
| body: | | |
| Auto-generated by **Catalog drift** ([workflow](https://github.com/${{ github.repository }}/actions/workflows/catalog-drift.yml)). | |
| Upstream sources changed; this PR re-runs the sync scripts: | |
| - `npm run sync:settings` | |
| - `npm run sync:env-vars` | |
| - `npm run sync:hooks` | |
| - `npm run sync:sub-agents` | |
| - `npm run sync:mcp` | |
| - `npm run sync:permissions` | |
| - `npm run sync:keybindings` | |
| - `npm run sync:cli-reference` | |
| Drifted catalogs: | |
| ``` | |
| ${{ steps.detect.outputs.drifted_files }} | |
| ``` | |
| Review the diff, then merge to land. If something looks wrong, | |
| close this PR and dig into the offending sync script — the | |
| `fetchedAt`-only case is normalised out before this body is | |
| generated, so anything here is real content drift. | |
| labels: | | |
| chore | |
| catalog-drift | |
| delete-branch: true |