Refresh guide: box #184
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: Guide draft | |
| on: | |
| issues: | |
| types: [labeled] | |
| jobs: | |
| draft: | |
| if: github.event.label.name == 'guide:draft' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 180 | |
| concurrency: | |
| group: guide-draft-issue-${{ github.event.issue.number }} | |
| cancel-in-progress: false | |
| permissions: | |
| contents: write | |
| issues: write | |
| pull-requests: write | |
| env: | |
| ISSUE_NUMBER: ${{ github.event.issue.number }} | |
| ISSUE_TITLE: ${{ github.event.issue.title }} | |
| ISSUE_BODY: ${{ github.event.issue.body }} | |
| GH_TOKEN: ${{ secrets.AGENT_PAT || secrets.GITHUB_TOKEN }} | |
| GH_REPO: ${{ github.repository }} | |
| steps: | |
| # Factory CLI lives in pipeline/; checkout main first so preflight can run | |
| # in TypeScript. Resume checkouts switch branch after preflight. | |
| - name: Checkout | |
| id: bootstrap_checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: main | |
| fetch-depth: 0 | |
| token: ${{ secrets.AGENT_PAT || secrets.GITHUB_TOKEN }} | |
| - name: Setup Node.js | |
| id: bootstrap_node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| cache: npm | |
| cache-dependency-path: pipeline/package-lock.json | |
| - name: Install dependencies | |
| id: bootstrap_install | |
| working-directory: pipeline | |
| run: npm ci | |
| - name: Ensure factory labels | |
| working-directory: pipeline | |
| run: npm run factory -- ensure-labels | |
| - name: Preflight existing factory PR | |
| id: preflight | |
| working-directory: pipeline | |
| run: npm run factory -- preflight | |
| - name: Refuse non-factory PR | |
| if: steps.preflight.outputs.refused == 'true' | |
| working-directory: pipeline | |
| env: | |
| REFUSED_PR_URL: ${{ steps.preflight.outputs.refused_pr_url }} | |
| run: npm run factory -- refuse | |
| - name: Transition labels | |
| if: steps.preflight.outputs.refused != 'true' | |
| working-directory: pipeline | |
| run: npm run factory -- transition-labels | |
| # issues:labeled always runs the workflow from the default branch, so a | |
| # stale resume branch (pre-rename layout, old tooling) will hard-fail at | |
| # npm ci / factory. Checkout resume + merge main (node_modules from main | |
| # install above stays on disk). | |
| - name: Checkout resume branch and sync main | |
| if: steps.preflight.outputs.refused != 'true' && steps.preflight.outputs.resume == 'true' | |
| working-directory: pipeline | |
| env: | |
| RESUME_BRANCH: ${{ steps.preflight.outputs.resume_branch }} | |
| run: npm run factory -- checkout-resume | |
| - name: Distill issue intent | |
| id: distill | |
| if: steps.preflight.outputs.refused != 'true' | |
| working-directory: pipeline | |
| env: | |
| OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }} | |
| run: npm run factory -- distill | |
| - name: Comment resolved intent | |
| if: steps.preflight.outputs.refused != 'true' && success() | |
| working-directory: pipeline | |
| env: | |
| SLUG: ${{ steps.distill.outputs.slug }} | |
| PROVIDER: ${{ steps.distill.outputs.provider }} | |
| PERSONA: ${{ steps.distill.outputs.persona }} | |
| NOTES: ${{ steps.distill.outputs.notes }} | |
| RESUME: ${{ steps.preflight.outputs.resume }} | |
| RESUME_PR_URL: ${{ steps.preflight.outputs.resume_pr_url }} | |
| RESUME_BRANCH: ${{ steps.preflight.outputs.resume_branch }} | |
| run: npm run factory -- comment-resolved | |
| - name: Create branch | |
| id: branch | |
| if: steps.preflight.outputs.refused != 'true' && success() | |
| working-directory: pipeline | |
| env: | |
| SLUG: ${{ steps.distill.outputs.slug }} | |
| RESUME: ${{ steps.preflight.outputs.resume }} | |
| RESUME_BRANCH: ${{ steps.preflight.outputs.resume_branch }} | |
| run: npm run factory -- create-branch | |
| - name: Draft guide | |
| id: draft | |
| if: steps.preflight.outputs.refused != 'true' && success() | |
| working-directory: pipeline | |
| env: | |
| OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }} | |
| PULSE_REGISTRY_KEY: ${{ secrets.PULSE_REGISTRY_KEY }} | |
| PULSE_REGISTRY_TENANT: ${{ secrets.PULSE_REGISTRY_TENANT }} | |
| SLUG: ${{ steps.distill.outputs.slug }} | |
| PERSONA: ${{ steps.distill.outputs.persona }} | |
| NOTES: ${{ steps.distill.outputs.notes }} | |
| run: npm run factory -- draft | |
| - name: Commit and push | |
| id: push | |
| if: steps.preflight.outputs.refused != 'true' && success() | |
| working-directory: pipeline | |
| env: | |
| BRANCH: ${{ steps.branch.outputs.name }} | |
| SLUG: ${{ steps.distill.outputs.slug }} | |
| OUTCOME: ${{ steps.draft.outputs.outcome }} | |
| run: npm run factory -- commit-push | |
| - name: Open or update PR | |
| id: open_pr | |
| if: steps.preflight.outputs.refused != 'true' && success() | |
| working-directory: pipeline | |
| env: | |
| BRANCH: ${{ steps.branch.outputs.name }} | |
| SLUG: ${{ steps.distill.outputs.slug }} | |
| PROVIDER: ${{ steps.distill.outputs.provider }} | |
| OUTCOME: ${{ steps.draft.outputs.outcome }} | |
| RESUME: ${{ steps.preflight.outputs.resume }} | |
| RESUME_PR_NUMBER: ${{ steps.preflight.outputs.resume_pr_number }} | |
| RESUME_PR_URL: ${{ steps.preflight.outputs.resume_pr_url }} | |
| run: npm run factory -- open-pr | |
| - name: Comment pipeline review or scope check on issue | |
| if: steps.preflight.outputs.refused != 'true' && success() | |
| working-directory: pipeline | |
| env: | |
| PR_URL: ${{ steps.open_pr.outputs.pr_url }} | |
| OUTCOME: ${{ steps.draft.outputs.outcome }} | |
| SLUG: ${{ steps.distill.outputs.slug }} | |
| run: npm run factory -- comment-review | |
| - name: Mark blocked on failure | |
| if: >- | |
| steps.preflight.outcome == 'success' && | |
| steps.preflight.outputs.refused != 'true' && | |
| failure() | |
| working-directory: pipeline | |
| env: | |
| RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| SLUG: ${{ steps.distill.outputs.slug }} | |
| PUSHED: ${{ steps.push.outputs.pushed }} | |
| BRANCH: ${{ steps.branch.outputs.name }} | |
| run: npm run factory -- mark-blocked | |
| - name: Always remove in-progress | |
| if: >- | |
| always() && | |
| steps.preflight.outcome == 'success' && | |
| steps.preflight.outputs.refused != 'true' | |
| working-directory: pipeline | |
| run: npm run factory -- cleanup | |
| # Pure gh — no Node. Covers checkout / setup-node / npm ci / early factory | |
| # failures before preflight succeeds (when mark-blocked cannot run). | |
| - name: Bootstrap failure fallback | |
| if: failure() && steps.preflight.outcome != 'success' | |
| env: | |
| RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| run: | | |
| set -euo pipefail | |
| gh label create "guide:blocked" --color "D73A4A" --description "Guide draft factory blocked" 2>/dev/null || true | |
| gh issue edit "$ISSUE_NUMBER" --remove-label "guide:draft" || true | |
| gh issue edit "$ISSUE_NUMBER" --remove-label "guide:in-progress" || true | |
| gh issue edit "$ISSUE_NUMBER" --add-label "guide:blocked" || true | |
| body_file="${RUNNER_TEMP:-/tmp}/bootstrap-fail.md" | |
| printf '%s\n' \ | |
| '`guide:draft` bootstrap failed before the factory CLI could run (checkout / Node / npm ci / early factory step).' \ | |
| '' \ | |
| "**Workflow run:** ${RUN_URL}" \ | |
| '' \ | |
| 'Fix the failure, then re-add `guide:draft`.' \ | |
| > "$body_file" | |
| gh issue comment "$ISSUE_NUMBER" --body-file "$body_file" |