docs_automation.yml

  1name: Documentation Automation
  2
  3on:
  4  # push:
  5  #   branches: [main]
  6  #   paths:
  7  #     - 'crates/**'
  8  #     - 'extensions/**'
  9  workflow_dispatch:
 10    inputs:
 11      pr_number:
 12        description: 'PR number to analyze (gets full PR diff)'
 13        required: false
 14        type: string
 15      trigger_sha:
 16        description: 'Commit SHA to analyze (ignored if pr_number is set)'
 17        required: false
 18        type: string
 19
 20permissions:
 21  contents: write
 22  pull-requests: write
 23
 24env:
 25  FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }}
 26  DROID_MODEL: claude-opus-4-5-20251101
 27
 28jobs:
 29  docs-automation:
 30    runs-on: ubuntu-latest
 31    timeout-minutes: 30
 32
 33    steps:
 34      - name: Checkout repository
 35        uses: actions/checkout@v4
 36        with:
 37          fetch-depth: 0
 38
 39      - name: Install Droid CLI
 40        id: install-droid
 41        run: |
 42          curl -fsSL https://app.factory.ai/cli | sh
 43          echo "${HOME}/.local/bin" >> "$GITHUB_PATH"
 44          echo "DROID_BIN=${HOME}/.local/bin/droid" >> "$GITHUB_ENV"
 45          # Verify installation
 46          "${HOME}/.local/bin/droid" --version
 47
 48      - name: Setup Node.js (for Prettier)
 49        uses: actions/setup-node@v4
 50        with:
 51          node-version: '20'
 52
 53      - name: Install Prettier
 54        run: npm install -g prettier
 55
 56      - name: Get changed files
 57        id: changed
 58        run: |
 59          if [ -n "${{ inputs.pr_number }}" ]; then
 60            # Get full PR diff
 61            echo "Analyzing PR #${{ inputs.pr_number }}"
 62            echo "source=pr" >> "$GITHUB_OUTPUT"
 63            echo "ref=${{ inputs.pr_number }}" >> "$GITHUB_OUTPUT"
 64            gh pr diff "${{ inputs.pr_number }}" --name-only > /tmp/changed_files.txt
 65          elif [ -n "${{ inputs.trigger_sha }}" ]; then
 66            # Get single commit diff
 67            SHA="${{ inputs.trigger_sha }}"
 68            echo "Analyzing commit $SHA"
 69            echo "source=commit" >> "$GITHUB_OUTPUT"
 70            echo "ref=$SHA" >> "$GITHUB_OUTPUT"
 71            git diff --name-only "${SHA}^" "$SHA" > /tmp/changed_files.txt
 72          else
 73            # Default to current commit
 74            SHA="${{ github.sha }}"
 75            echo "Analyzing commit $SHA"
 76            echo "source=commit" >> "$GITHUB_OUTPUT"
 77            echo "ref=$SHA" >> "$GITHUB_OUTPUT"
 78            git diff --name-only "${SHA}^" "$SHA" > /tmp/changed_files.txt || git diff --name-only HEAD~1 HEAD > /tmp/changed_files.txt
 79          fi
 80
 81          echo "Changed files:"
 82          cat /tmp/changed_files.txt
 83        env:
 84          GH_TOKEN: ${{ github.token }}
 85
 86      # Phase 3: Analyze Changes (Read-Only - default)
 87      - name: "Phase 3: Analyze Changes"
 88        id: phase3
 89        run: |
 90          CHANGED_FILES=$(tr '\n' ' ' < /tmp/changed_files.txt)
 91          echo "Analyzing changes in: $CHANGED_FILES"
 92
 93          "$DROID_BIN" exec \
 94            -m "$DROID_MODEL" \
 95            "$(cat .factory/prompts/docs-automation/phase3-analyze.md)
 96
 97            Changed files: $CHANGED_FILES" \
 98            > /tmp/phase3-output.md 2>&1 || true
 99          echo "Change analysis complete"
100          cat /tmp/phase3-output.md
101
102      # Phase 4: Plan Documentation Impact (Read-Only - default)
103      - name: "Phase 4: Plan Documentation Impact"
104        id: phase4
105        run: |
106          CHANGED_FILES=$(tr '\n' ' ' < /tmp/changed_files.txt)
107          PHASE3_OUTPUT=$(cat /tmp/phase3-output.md)
108          
109          "$DROID_BIN" exec \
110            -m "$DROID_MODEL" \
111            "$(cat .factory/prompts/docs-automation/phase4-plan.md)
112
113            ## Context from Phase 3
114
115            ### Changed Files
116            $CHANGED_FILES
117
118            ### Phase 3 Analysis
119            $PHASE3_OUTPUT" \
120            > /tmp/phase4-plan.md 2>&1 || true
121          echo "Documentation plan complete"
122          cat /tmp/phase4-plan.md
123
124          # Check if updates are required
125          if grep -q "NO_UPDATES_REQUIRED" /tmp/phase4-plan.md; then
126            echo "updates_required=false" >> "$GITHUB_OUTPUT"
127          else
128            echo "updates_required=true" >> "$GITHUB_OUTPUT"
129          fi
130
131      # Phase 5: Apply Plan (Write-Enabled with --auto medium)
132      - name: "Phase 5: Apply Documentation Plan"
133        id: phase5
134        if: steps.phase4.outputs.updates_required == 'true'
135        run: |
136          PHASE4_PLAN=$(cat /tmp/phase4-plan.md)
137          
138          "$DROID_BIN" exec \
139            -m "$DROID_MODEL" \
140            --auto medium \
141            "$(cat .factory/prompts/docs-automation/phase5-apply.md)
142
143            ## Documentation Plan from Phase 4
144            $PHASE4_PLAN" \
145            > /tmp/phase5-report.md 2>&1 || true
146          echo "Documentation updates applied"
147          cat /tmp/phase5-report.md
148
149      # Phase 5b: Format with Prettier
150      - name: "Phase 5b: Format with Prettier"
151        id: phase5b
152        if: steps.phase4.outputs.updates_required == 'true'
153        run: |
154          echo "Formatting documentation with Prettier..."
155          cd docs && prettier --write src/
156
157          echo "Verifying Prettier formatting passes..."
158          cd docs && prettier --check src/
159
160          echo "Prettier formatting complete"
161
162      # Phase 6: Summarize Changes (Read-Only - default)
163      - name: "Phase 6: Summarize Changes"
164        id: phase6
165        if: steps.phase4.outputs.updates_required == 'true'
166        run: |
167          # Get git diff of docs
168          git diff docs/src/ > /tmp/docs-diff.txt || true
169          
170          PHASE5_REPORT=$(cat /tmp/phase5-report.md)
171          DOCS_DIFF=$(cat /tmp/docs-diff.txt)
172
173          "$DROID_BIN" exec \
174            -m "$DROID_MODEL" \
175            "$(cat .factory/prompts/docs-automation/phase6-summarize.md)
176
177            ## Context
178
179            ### Phase 5 Report
180            $PHASE5_REPORT
181
182            ### Documentation Diff
183            \`\`\`diff
184            $DOCS_DIFF
185            \`\`\`" \
186            > /tmp/phase6-summary.md 2>&1 || true
187          echo "Summary generated"
188          cat /tmp/phase6-summary.md
189
190      # Phase 7: Commit and Open PR
191      - name: "Phase 7: Create PR"
192        id: phase7
193        if: steps.phase4.outputs.updates_required == 'true'
194        run: |
195          # Check if there are actual changes
196          if git diff --quiet docs/src/; then
197            echo "No documentation changes detected"
198            exit 0
199          fi
200
201          # Configure git
202          git config user.name "factory-droid[bot]"
203          git config user.email "138933559+factory-droid[bot]@users.noreply.github.com"
204
205          # Daily batch branch - one branch per day, multiple commits accumulate
206          BRANCH_NAME="docs/auto-update-$(date +%Y-%m-%d)"
207
208          # Get source PR info for attribution
209          SOURCE_PR_INFO=""
210          if [ "${{ steps.changed.outputs.source }}" == "pr" ]; then
211            PR_NUM="${{ steps.changed.outputs.ref }}"
212            PR_DETAILS=$(gh pr view "$PR_NUM" --json title,author,url 2>/dev/null || echo "{}")
213            SOURCE_TITLE=$(echo "$PR_DETAILS" | jq -r '.title // "Unknown"')
214            SOURCE_AUTHOR=$(echo "$PR_DETAILS" | jq -r '.author.login // "Unknown"')
215            SOURCE_URL=$(echo "$PR_DETAILS" | jq -r '.url // ""')
216            SOURCE_PR_INFO="
217          ---
218          **Source**: [#$PR_NUM]($SOURCE_URL) - $SOURCE_TITLE
219          **Author**: @$SOURCE_AUTHOR
220          "
221          fi
222
223          # Stash local changes from phase 5
224          git stash push -m "docs-automation-changes" -- docs/src/
225
226          # Check if branch already exists on remote
227          if git ls-remote --exit-code --heads origin "$BRANCH_NAME" > /dev/null 2>&1; then
228            echo "Branch $BRANCH_NAME exists, checking out and updating..."
229            git fetch origin "$BRANCH_NAME"
230            git checkout -B "$BRANCH_NAME" "origin/$BRANCH_NAME"
231          else
232            echo "Creating new branch $BRANCH_NAME..."
233            git checkout -b "$BRANCH_NAME"
234          fi
235
236          # Apply stashed changes
237          git stash pop || true
238
239          # Stage and commit
240          git add docs/src/
241          SUMMARY=$(head -50 < /tmp/phase6-summary.md)
242          git commit -m "docs: auto-update documentation
243
244          ${SUMMARY}
245
246          Triggered by: ${{ steps.changed.outputs.source }} ${{ steps.changed.outputs.ref }}
247
248          Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>"
249
250          # Push
251          git push -u origin "$BRANCH_NAME"
252
253          # Build the PR body section for this update
254          PR_BODY_SECTION="## Update from $(date '+%Y-%m-%d %H:%M')
255          $SOURCE_PR_INFO
256          $(cat /tmp/phase6-summary.md)
257          "
258
259          # Check if PR already exists for this branch
260          EXISTING_PR=$(gh pr list --head "$BRANCH_NAME" --json number,url,body --jq '.[0]' || echo "")
261
262          if [ -n "$EXISTING_PR" ] && [ "$EXISTING_PR" != "null" ]; then
263            PR_NUM=$(echo "$EXISTING_PR" | jq -r '.number')
264            PR_URL=$(echo "$EXISTING_PR" | jq -r '.url')
265            EXISTING_BODY=$(echo "$EXISTING_PR" | jq -r '.body // ""')
266            
267            # Append new summary to existing PR body
268            NEW_BODY="${EXISTING_BODY}
269
270          ---
271
272          ${PR_BODY_SECTION}"
273            
274            echo "$NEW_BODY" > /tmp/updated-pr-body.md
275            gh pr edit "$PR_NUM" --body-file /tmp/updated-pr-body.md
276            
277            echo "PR #$PR_NUM updated: $PR_URL"
278          else
279            # Create new PR
280            echo "$PR_BODY_SECTION" > /tmp/new-pr-body.md
281            gh pr create \
282              --title "docs: automated documentation update ($(date +%Y-%m-%d))" \
283              --body-file /tmp/new-pr-body.md \
284              --base main || true
285            echo "PR created on branch: $BRANCH_NAME"
286          fi
287        env:
288          GH_TOKEN: ${{ github.token }}
289
290      # Summary output
291      - name: "Summary"
292        if: always()
293        run: |
294          echo "## Documentation Automation Summary" >> "$GITHUB_STEP_SUMMARY"
295          echo "" >> "$GITHUB_STEP_SUMMARY"
296
297          if [ "${{ steps.phase4.outputs.updates_required }}" == "false" ]; then
298            echo "No documentation updates required for this change." >> "$GITHUB_STEP_SUMMARY"
299          elif [ -f /tmp/phase6-summary.md ]; then
300            cat /tmp/phase6-summary.md >> "$GITHUB_STEP_SUMMARY"
301          else
302            echo "Workflow completed. Check individual phase outputs for details." >> "$GITHUB_STEP_SUMMARY"
303          fi