docs-updated #28
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: Generate Rules from Docs | |
| # Regenerates docs-driven skill rules and opens a sync PR. Triggered when the | |
| # documentation repo deploys (repository_dispatch), on a weekly safety-net | |
| # cron, or manually. See docs/plans-archive/docs-driven-skills.md (Phase 2). | |
| # | |
| # Offline-first: the docs repo is checked out and built in-job; the generator | |
| # reads the local build output and never fetches docs over the network. | |
| on: | |
| repository_dispatch: | |
| types: [docs-updated] | |
| schedule: | |
| # Mondays 09:17 UTC — safety net in case a dispatch is missed. | |
| - cron: '17 9 * * 1' | |
| workflow_dispatch: | |
| inputs: | |
| docs_ref: | |
| description: 'Docs ref to build (branch, tag, or commit SHA)' | |
| required: false | |
| default: 'main' | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| issues: write | |
| concurrency: | |
| group: generate-rules | |
| cancel-in-progress: false | |
| jobs: | |
| generate: | |
| name: Generate and open sync PR | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Mint app token | |
| id: app-token | |
| uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 | |
| with: | |
| app-id: ${{ secrets.SKILLS_SYNC_APP_ID }} | |
| private-key: ${{ secrets.SKILLS_SYNC_APP_PRIVATE_KEY }} | |
| - name: Checkout skills | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| path: skills | |
| fetch-depth: 0 | |
| # Use the App token so the branch push (and thus the auto-PR) is | |
| # attributed to the App and triggers the validate workflow. | |
| token: ${{ steps.app-token.outputs.token }} | |
| - name: Resolve docs ref | |
| id: docsref | |
| run: | | |
| REF="${{ github.event.client_payload.sha || github.event.inputs.docs_ref || 'main' }}" | |
| echo "ref=$REF" >> "$GITHUB_OUTPUT" | |
| echo "Docs ref: $REF" | |
| - name: Checkout documentation | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| repository: HarperFast/documentation | |
| ref: ${{ steps.docsref.outputs.ref }} | |
| path: documentation | |
| # documentation is a public repo, so the default job token reads it. | |
| # Full history (fetch-depth: 0) so the sync PR body can resolve the | |
| # docs commit range since each rule's recorded baseline. | |
| fetch-depth: 0 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 | |
| with: | |
| node-version-file: skills/.nvmrc | |
| - name: Build documentation (produces flat-markdown under build/) | |
| working-directory: documentation | |
| run: | | |
| npm ci | |
| npm run build | |
| - name: Install skills dependencies | |
| working-directory: skills | |
| run: npm ci | |
| - name: Capture pre-sync provenance | |
| working-directory: skills | |
| # Snapshot each rule's recorded baseline (sourceCommit/inputHash) and | |
| # which rules are stale, BEFORE generation overwrites the frontmatter. | |
| # The PR step reads this snapshot to explain why each rule changed. | |
| # Non-strict: this never blocks the sync — the generator is the gate. | |
| run: npm run sync:report -- --docs-path ../documentation --out ../sync-provenance.json | |
| - name: Generate rules | |
| working-directory: skills | |
| env: | |
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | |
| run: npm run generate -- --docs-path ../documentation | |
| - name: Validate | |
| working-directory: skills | |
| run: | | |
| npm run format:check | |
| npm run build | |
| node scripts/validate-skills.mjs | |
| node scripts/generation/validate-generated.mjs --docs-path ../documentation | |
| - name: Report generation failure | |
| if: failure() | |
| working-directory: skills | |
| env: | |
| GH_TOKEN: ${{ steps.app-token.outputs.token || github.token }} | |
| run: | | |
| set -euo pipefail | |
| # Resolve the actual docs commit SHA (falls back to the ref if the | |
| # documentation checkout failed before we could read HEAD). | |
| DOCS_SHA=$(git -C ../documentation rev-parse HEAD 2>/dev/null || echo "${{ steps.docsref.outputs.ref }}") | |
| SHORT="${DOCS_SHA:0:7}" | |
| TITLE="Auto-sync generation failure (${SHORT})" | |
| # Idempotent by docs SHA: if an open issue for this SHA already | |
| # exists, add a comment rather than opening a duplicate. | |
| EXISTING=$(gh issue list --state open --json number,title \ | |
| --jq ".[] | select(.title == \"${TITLE}\") | .number" 2>/dev/null | head -1 || echo "") | |
| if [ -n "$EXISTING" ]; then | |
| echo "Existing failure issue #${EXISTING} — appending comment." | |
| gh issue comment "$EXISTING" --body \ | |
| "Generation failed again for \`${DOCS_SHA}\` at $(date -u +%Y-%m-%dT%H:%M:%SZ). Check the [workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details." | |
| else | |
| NEW_ISSUE_URL=$(gh issue create \ | |
| --title "${TITLE}" \ | |
| --body "Auto-sync generation failed for docs SHA \`${DOCS_SHA}\`. | |
| Opened automatically by \`.github/workflows/generate.yaml\`. Check the [workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for the full error. | |
| **Common causes and fixes (Story 4 in docs/plans-archive/docs-driven-skills.md):** | |
| - Source file renamed or moved — update \`sources[].path\` in \`rules.manifest.yaml\`. | |
| - Section heading renamed or removed — update \`sources[].section\`. | |
| - Must-cover assertion no longer in source — update \`must_cover\` or the section changed. | |
| After fixing, regenerate locally (\`npm run generate -- --docs-path <docs>\`) and open a fix PR.") | |
| echo "Opened failure issue ${NEW_ISSUE_URL}" | |
| # Set the org-level "Bug" issue type (replaces the retired `bug` label). | |
| NEW_ISSUE_NUM=$(printf '%s' "$NEW_ISSUE_URL" | grep -oE '[0-9]+$') | |
| if [ -n "$NEW_ISSUE_NUM" ]; then | |
| gh api -X PATCH "/repos/${{ github.repository }}/issues/${NEW_ISSUE_NUM}" \ | |
| -f type="Bug" || echo "Could not set issue type (non-fatal)." | |
| fi | |
| fi | |
| - name: Open or update sync PR | |
| working-directory: skills | |
| env: | |
| # The App token (not the default job token) ensures the auto-PR | |
| # triggers the validate workflow — PRs opened by the job token do | |
| # not trigger other workflows. | |
| GH_TOKEN: ${{ steps.app-token.outputs.token }} | |
| run: | | |
| set -euo pipefail | |
| # The generator only writes rule bodies + AGENTS.md under this path. | |
| if git diff --quiet -- harper-best-practices; then | |
| echo "No changes — skills are already in sync with docs." | |
| exit 0 | |
| fi | |
| DOCS_SHA=$(git -C ../documentation rev-parse HEAD) | |
| SHORT=${DOCS_SHA:0:7} | |
| BRANCH="auto/docs-sync" | |
| git config user.name "${{ steps.app-token.outputs.app-slug }}[bot]" | |
| git config user.email "${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com" | |
| # Single rolling branch: at most one open auto-sync PR at a time. If | |
| # docs change again before it merges, the same PR is force-updated. | |
| git checkout -B "$BRANCH" | |
| git add harper-best-practices | |
| git commit -m "docs: regenerate rules from documentation@${SHORT}" | |
| # Force is intentional: each run replaces the rolling branch's prior | |
| # commit in place so the open sync PR always reflects the latest docs. | |
| git push --force origin "$BRANCH" | |
| # Compose a provenance-rich body from the pre-sync snapshot: which | |
| # rules changed, the docs commit each was last synced from, and the | |
| # full docs commit range since the oldest such baseline. | |
| BODY="$(npm run --silent sync:report -- \ | |
| --docs-path ../documentation \ | |
| --format pr-body \ | |
| --from ../sync-provenance.json)" | |
| STATE=$(gh pr view "$BRANCH" --json state --jq .state 2>/dev/null || echo "") | |
| if [ "$STATE" = "OPEN" ]; then | |
| echo "Existing sync PR updated." | |
| gh pr edit "$BRANCH" \ | |
| --title "docs: regenerate rules from documentation@${SHORT}" \ | |
| --body "$BODY" | |
| else | |
| gh pr create --base main --head "$BRANCH" \ | |
| --title "docs: regenerate rules from documentation@${SHORT}" \ | |
| --body "$BODY" | |
| fi |