Docs validation (agent) #19
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: Docs validation (agent) | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| mode: | |
| description: 'Validation mode' | |
| required: true | |
| type: choice | |
| default: 'api' | |
| options: | |
| - 'api' | |
| - 'guide' | |
| section: | |
| description: '[api mode] API section to validate (ignored in guide mode)' | |
| required: false | |
| type: choice | |
| default: 'Model Class' | |
| options: | |
| - 'Configuration' | |
| - 'Controller' | |
| - 'Global Helpers' | |
| - 'Migrator' | |
| - 'Model Class' | |
| - 'Model Configuration' | |
| - 'Model Object' | |
| - 'View Helpers' | |
| directory: | |
| description: '[guide mode] Top-level guides directory to validate (ignored in api mode)' | |
| required: false | |
| type: choice | |
| default: 'upgrading' | |
| options: | |
| - 'basics' | |
| - 'command-line-tools' | |
| - 'contributing' | |
| - 'core-concepts' | |
| - 'deployment' | |
| - 'digging-deeper' | |
| - 'start-here' | |
| - 'testing' | |
| - 'upgrading' | |
| limit: | |
| description: 'Max items to validate this run (blank = all pending)' | |
| required: false | |
| type: string | |
| default: '' | |
| model: | |
| description: 'Anthropic model id' | |
| required: false | |
| type: string | |
| default: 'claude-sonnet-4-6' | |
| dry_run: | |
| description: 'List targets without invoking the agent' | |
| required: false | |
| type: boolean | |
| default: false | |
| force: | |
| description: 'Re-run already-done items (overwrites their state.json entries)' | |
| required: false | |
| type: boolean | |
| default: false | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| concurrency: | |
| group: docs-validation-${{ inputs.mode }}-${{ inputs.section || inputs.directory }} | |
| cancel-in-progress: false | |
| jobs: | |
| validate: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 240 | |
| steps: | |
| - name: Checkout dispatching ref | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: 22 | |
| - name: Install tools/docs-validation deps | |
| working-directory: tools/docs-validation | |
| run: npm ci --no-audit --no-fund | |
| - name: Set up pnpm | |
| if: ${{ inputs.mode == 'guide' }} | |
| uses: pnpm/action-setup@v5 | |
| with: | |
| version: 10.23.0 | |
| - name: Install web pnpm deps (for verify-docs harness in guide mode) | |
| if: ${{ inputs.mode == 'guide' }} | |
| working-directory: web | |
| run: pnpm install --frozen-lockfile | |
| - name: Set up Linuxbrew | |
| uses: Homebrew/actions/setup-homebrew@master | |
| - name: Install Wheels CLI | |
| run: | | |
| brew tap wheels-dev/wheels || true | |
| brew install wheels | |
| - name: Patch wheels wrapper JAVA_HOME for Linux | |
| run: | | |
| WRAPPER="$(brew --prefix wheels)/bin/wheels" | |
| sed -i 's|/openjdk.jdk/Contents/Home||g' "$WRAPPER" | |
| grep JAVA_HOME "$WRAPPER" | |
| - name: Warm up wheels module | |
| run: wheels --version >/dev/null | |
| - name: Compute branch name + commit message | |
| id: branch | |
| run: | | |
| if [[ "${{ inputs.mode }}" == "guide" ]]; then | |
| slug=$(echo "${{ inputs.directory }}" | tr '[:upper:] ' '[:lower:]-') | |
| echo "name=docs-validation/guide-${slug}-${{ github.run_id }}" >> "$GITHUB_OUTPUT" | |
| echo "title=docs(guides): validate ${{ inputs.directory }} directory" >> "$GITHUB_OUTPUT" | |
| echo "msg=docs(guides): validate ${{ inputs.directory }} directory (run ${{ github.run_id }})" >> "$GITHUB_OUTPUT" | |
| echo "label=${{ inputs.directory }} directory" >> "$GITHUB_OUTPUT" | |
| else | |
| slug=$(echo "${{ inputs.section }}" | tr '[:upper:] ' '[:lower:]-') | |
| echo "name=docs-validation/${slug}-${{ github.run_id }}" >> "$GITHUB_OUTPUT" | |
| echo "title=docs(api): validate ${{ inputs.section }} section" >> "$GITHUB_OUTPUT" | |
| echo "msg=docs(api): validate ${{ inputs.section }} section (run ${{ github.run_id }})" >> "$GITHUB_OUTPUT" | |
| echo "label=${{ inputs.section }} section" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Configure git | |
| run: | | |
| git config user.name "wheels-docs-validator[bot]" | |
| git config user.email "wheels-docs-validator@users.noreply.github.com" | |
| git checkout -b "${{ steps.branch.outputs.name }}" | |
| - name: List targets (dry-run) | |
| if: ${{ inputs.dry_run }} | |
| working-directory: tools/docs-validation | |
| run: | | |
| ARGS=(--mode "${{ inputs.mode }}" --dry-run) | |
| if [[ "${{ inputs.mode }}" == "guide" ]]; then | |
| ARGS+=(--directory "${{ inputs.directory }}") | |
| else | |
| ARGS+=(--section "${{ inputs.section }}") | |
| fi | |
| if [[ -n "${{ inputs.limit }}" ]]; then | |
| ARGS+=(--limit "${{ inputs.limit }}") | |
| fi | |
| if [[ "${{ inputs.force }}" == "true" ]]; then | |
| ARGS+=(--force) | |
| fi | |
| node orchestrate.mjs "${ARGS[@]}" | |
| - name: Run agent | |
| if: ${{ !inputs.dry_run }} | |
| working-directory: tools/docs-validation | |
| env: | |
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | |
| WHEELS_DOCS_MODEL: ${{ inputs.model }} | |
| WHEELS_DOCS_MAX_TURNS: '24' | |
| LUCLI_HOME: ${{ runner.temp }}/.wheels | |
| WHEELS_FRAMEWORK_PATH: ${{ github.workspace }}/vendor/wheels | |
| run: | | |
| ARGS=(--mode "${{ inputs.mode }}") | |
| if [[ "${{ inputs.mode }}" == "guide" ]]; then | |
| ARGS+=(--directory "${{ inputs.directory }}") | |
| else | |
| ARGS+=(--section "${{ inputs.section }}") | |
| fi | |
| if [[ -n "${{ inputs.limit }}" ]]; then | |
| ARGS+=(--limit "${{ inputs.limit }}") | |
| fi | |
| if [[ "${{ inputs.force }}" == "true" ]]; then | |
| ARGS+=(--force) | |
| fi | |
| node orchestrate.mjs "${ARGS[@]}" | |
| - name: Show diff | |
| if: ${{ !inputs.dry_run }} | |
| run: | | |
| git status | |
| git --no-pager diff --stat | |
| echo "---" | |
| git --no-pager diff | head -500 || true | |
| - name: Commit + push if changes | |
| if: ${{ !inputs.dry_run }} | |
| id: commit | |
| run: | | |
| if git diff --quiet && git diff --cached --quiet; then | |
| echo "No changes produced by the agent." | |
| echo "changed=false" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| git add -A | |
| git commit -m "${{ steps.branch.outputs.msg }}" | |
| git push -u origin "${{ steps.branch.outputs.name }}" | |
| echo "changed=true" >> "$GITHUB_OUTPUT" | |
| - name: Open draft PR | |
| if: ${{ !inputs.dry_run && steps.commit.outputs.changed == 'true' }} | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| BODY=$(cat <<'EOF' | |
| ## Summary | |
| Agent-driven docs validation for the **${{ steps.branch.outputs.label }}**. | |
| - Mode: `${{ inputs.mode }}` | |
| - Edit scope (api mode): `vendor/wheels/**/*.cfc` (docblocks + narrow body fixes) and `vendor/wheels/public/docs/reference/{scope}/{name}.txt` | |
| - Edit scope (guide mode): `web/sites/guides/src/content/docs/v4-0-0-snapshot/**/*.mdx?` and `vendor/wheels/**/*.cfc` (docblock prose only) | |
| - Per-item status tracked in `tools/docs-validation/state.json` | |
| ## Review checklist | |
| - [ ] Each `state.json` entry with `status: done` has the corresponding reference file (api) or annotated guide page (guide) | |
| - [ ] No CFC function signature changes (agent is forbidden from changing them — verify anyway) | |
| - [ ] Any docblock/body edits hold up against the test suite | |
| - [ ] `state.json` items with `status: needs_human` describe the open question in `notes` | |
| EOF | |
| ) | |
| gh pr create \ | |
| --base develop \ | |
| --head "${{ steps.branch.outputs.name }}" \ | |
| --title "${{ steps.branch.outputs.title }}" \ | |
| --body "$BODY" \ | |
| --draft | |
| - name: Upload state artifact | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: docs-validation-state-${{ github.run_id }} | |
| path: tools/docs-validation/state.json | |
| if-no-files-found: ignore |